簡介
此示例是記錄學習SwiftUI的過程,原文出自
SwiftUI Essentials Building Lists and Navigation。
在SwiftUI 01-創(chuàng)建和組合視圖 (Creating and Combining Views) 中創(chuàng)建了landmark
的詳情頁站粟,本節(jié)我們做landmark
的列表頁怪得。
我們將創(chuàng)建可以顯示任何landmark
信息的視圖,并動態(tài)生成一個滾動列表,用戶可以點擊該列表查看landmark
的詳細視圖只恨。要微調UI贴膘,我們將使用Xcode的畫布以不同的設備大小呈現(xiàn)多個預覽卖子。
下載項目文件以開始構建此項目,并按照以下步驟操作刑峡。
第一節(jié) 了解示例中的數據
在第一個教程中洋闽, 我們直接把數據寫死在視圖的代碼中。現(xiàn)在我們將學習把數據傳遞給自定義視圖以供其顯示突梦。
首先下載入門項目并熟悉示例數據诫舅。
1.在Project導航器中,選擇
Models> Landmark.swift
宫患。
Landmark.swift
聲明了一個Landmark
的結構體刊懈,用于存儲應用程序需要顯示的所有landmark
信息,根據landmarkData.json
中其中一組數據的所有字段構建的landmark
的model娃闲。2.在Project導航器中虚汛,選擇
Resources> landmarkData.json
。
我們將在本教程的其余部分以及隨后的所有內容中使用此示例數據皇帮。
- 3.請注意卷哩,創(chuàng)建和組合視圖中的
ContentView
類現(xiàn)在被修改為LandmarkDetail
。
我們將在此以及以下每個教程中創(chuàng)建多個view的類属拾。
第2節(jié) 創(chuàng)建行視圖
我們將在本教程中構建的第一個視圖是用于顯示每個地標的詳細信息的行将谊。 此行視圖將信息存儲在其顯示的地標的屬性中冷溶,以便一個視圖可以顯示任何地標。 稍后瓢娜,您將多個行組合成一個地標列表挂洛。
1.創(chuàng)建一個名為
LandmarkRow.swift
的新SwiftUI
視圖。2.如果預覽不可見眠砾,請通過選擇
Editor > Editor and Canvas
來顯示畫布虏劲,然后單擊Get Started
。3.添加
LandmarkRow
類中添加一個Landmark
類型的landmark
屬性作為這個視圖的模型褒颈。
import SwiftUI
struct LandmarkRow : View {
var landmark: Landmark
var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello World!"/*@END_MENU_TOKEN@*/)
}
}
#if DEBUG
struct LandmarkRow_Previews : PreviewProvider {
static var previews: some View {
LandmarkRow()
}
}
#endif
添加landmark
屬性時柒巫,預覽將停止工作,因為LandmarkRow
類型在初始化期間需要傳入一個Landmark
的模型谷丸。
要修復預覽堡掏,我們需要修改預覽中初始化LandmarkRow()
的代碼。
- 4.在
LandmarkRow_Previews
的預覽靜態(tài)屬性中刨疼,將landmark
參數添加到LandmarkRow()
初始化方法的參數中泉唁,指定landmarkData
數組的第一個元素。
預覽顯示文本Hello World揩慕。
修復后亭畜,您可以為行構建布局。
5.將現(xiàn)有文本視圖嵌入HStack中迎卤。
6.修改文本視圖以使用
landmark
屬性的name
拴鸵。7.通過在文本視圖之前添加圖像來完成行。
第3節(jié) 自定義行預覽
Xcode的畫布自動識別并顯示當前編輯器中符合PreviewProvider協(xié)議的任何類型蜗搔。 預覽提供程序返回一個或多個視圖劲藐,其中包含用于配置大小和設備的選項。
我們可以從預覽提供程序自定義返回的內容樟凄,以準確呈現(xiàn)對我們最有幫助的預覽聘芜。
- 1.在
LandmarkRow_Previews
中,將landmark
參數更新為landmarkData
數組中的第二個元素缝龄。
修改后預覽立即更改以顯示第二個model的數據而不是第一個汰现。
- 2.使用
previewLayout(_ :)
方法設置列表中這一行的cell大小。
我們可以在previews
中使用group
返回多行cell預覽二拐。
- 3.將返回的行換放到一個
Group
中服鹅,然后在Group
中再添加一個LandmarkRow
。
Group
是用于對視圖內容進行分組的容器百新。 Xcode將組的子視圖渲染為畫布中的單獨預覽企软。
- 4.要簡化代碼,請將
previewLayout(_ :)
調用移動到Group
的子聲明外部饭望。
import SwiftUI
struct LandmarkRow : View {
var landmark: Landmark
var body: some View {
HStack {
landmark.image(forSize: 50)
Text(landmark.name)
}
}
}
#if DEBUG
struct LandmarkRow_Previews : PreviewProvider {
static var previews: some View {
Group {
LandmarkRow(landmark: landmarkData[0])
LandmarkRow(landmark: landmarkData[1])
}
.previewLayout(.fixed(width: 300, height: 70))
}
}
#endif
視圖的子項繼承視圖的上下文設置仗哨,例如預覽配置形庭。
我們在預覽提供程序中編寫的代碼僅更改Xcode
在畫布中顯示的內容。 由于#if DEBUG
指令厌漂,編譯器會刪除代碼萨醒,因此它在release下不會打包到應用程序中。
第4節(jié) 創(chuàng)建landmark
列表
使用SwiftUI
的List
類型時苇倡,可以顯示特定于平臺的視圖列表富纸。 列表的元素可以是靜態(tài)的,就像我們目前創(chuàng)建的堆棧的子視圖一樣旨椒,或者是動態(tài)生成的晓褪。 您甚至可以混合靜態(tài)和動態(tài)生成的視圖。
1.創(chuàng)建一個名為
LandmarkList.swift
的新SwiftUI
視圖综慎。2.用
List
替換模板中默認的Text
視圖涣仿,并在List
中添加兩個LandmarkRow
初始化的視圖作為兩行顯示。
現(xiàn)在我們預覽顯示以適合iOS的列表樣式呈現(xiàn)的兩個landmark
示惊,這個List
不就是UITableView
嗎好港?
第5節(jié) 使列表動態(tài)化
我們可以直接從集合中生成行,而不是單獨指定列表的元素米罚。
我們可以通過傳遞數據集合以及為集合中的每個元素提供視圖的閉包來創(chuàng)建顯示集合元素的列表钧汹。 該列表使用提供的閉包將集合中的每個元素轉換為子視圖。
- 1.刪除
List
中兩個靜態(tài)的LandmarkRow
阔拳,然后將landmarkData
傳遞給List
的初始化方法中崭孤。
列表使用可識別的數據类嗤。 我們可以通過以下兩種方式之一來識別數據:通過調用identified(by:)
方法糊肠,使用唯一標識每個元素的屬性的鍵路徑,或者使您的數據類型遵守Identifiable
協(xié)議遗锣。
- 2.通過從閉包返回
LandmarkRow
來完成動態(tài)生成的列表货裹。
這為landmarkData
數組中的每個元素創(chuàng)建一個LandmarkRow
。
接下來精偿,我們將通過向Landmark
類型添加Identifiable
協(xié)議來簡化List
代碼弧圆。
- 3.切換到
Landmark.swift并
聲明符合可識別協(xié)議。
由于Landmark
類型已經具有Identifiable
協(xié)議所需的id
屬性笔咽,因此沒有其他工作要做搔预。
- 4.切換回
LandmarkList.swift
并刪除landmarkData
調用的identified(by:)
方法。
import SwiftUI
struct LandmarkList : View {
var body: some View {
List(landmarkData) { landmark in
LandmarkRow(landmark: landmark)
}
}
}
#if DEBUG
struct LandmarkList_Previews : PreviewProvider {
static var previews: some View {
LandmarkList()
}
}
#endif
從現(xiàn)在開始叶组,我們將能夠直接使用Landmark
元素的集合拯田。
第6節(jié) 在列表和詳細信息之間設置導航
列表正確呈現(xiàn),但我們無法點擊某個landmark
以查看它的詳細信息頁面甩十。
通過將導航功能嵌入到NavigationView
中船庇,然后將每行嵌套在NavigationButton
中以設置到目標視圖的轉換吭产,可以將導航功能添加到列表中。
1.在
NavigationView
中嵌入動態(tài)生成的landmark
列表鸭轮。2.調用
navigationBarTitle(_ :)
修飾符方法以在顯示列表時設置導航欄的標題臣淤。
3.在列表的閉包內,將返回的行包裝在
NavigationButton
中窃爷,將LandmarkDetail
視圖指定為目標邑蒋。4.我們可以通過切換到實時模式直接在預覽中嘗試導航。 單擊“實時預覽”按鈕按厘,然后點擊地標以訪問詳細信息頁面寺董。
第7節(jié) 將數據傳遞到子視圖
LandmarkDetail
視圖中是使用寫死的數據展示UI 的。 就像LandmarkRow
一樣刻剥,我們需要在LandmarkDetail
中添加一個Landmark
類型的模型以作為其數據源顯示view遮咖。
從子視圖開始,我們將轉換CircleImage
造虏,MapView
和LandmarkDetail
以顯示傳入的數據御吞,而不是將數據寫死在代碼中。
- 1.在
CircleImage.swift
中漓藕,將存儲的圖像屬性添加到CircleImage
陶珠。
import SwiftUI
struct CircleImage : View {
var image: Image
var body: some View {
image
// 給圖片添加圓角
.clipShape(Circle())
// 給圓角添加邊框
.overlay(Circle().stroke(Color.gray, lineWidth: 4))
// 添加半徑為10的陰影
.shadow(radius: 10)
}
}
#if DEBUG
struct CircleImage_Previews : PreviewProvider {
static var previews: some View {
CircleImage(image: Image("turtlerock"))
}
}
#endif
這是使用SwiftUI
構建視圖時的常見模式。 自定義視圖通常會包裝并封裝特定視圖的一系列修飾符享钞。
- 2.更新
CircleImage_Previews
揍诽,給其傳遞一個Turtle Rock
的圖像使其可以正常預覽。
- 3.在
MapView.swift
中栗竖,向MapView
添加一個坐標屬性暑脆,并轉換代碼以使用該屬性,而不是在代碼中寫死緯度和經度數據狐肢。
import SwiftUI
import MapKit
struct MapView: UIViewRepresentable {
var coordinate: CLLocationCoordinate2D
func makeUIView(context: Context) -> MKMapView {
MKMapView(frame: .zero)
}
func updateUIView(_ view: MKMapView, context: Context) {
let span = MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02)
let region = MKCoordinateRegion(center: coordinate, span: span)
view.setRegion(region, animated: true)
}
}
struct MapView_Preview: PreviewProvider {
static var previews: some View {
MapView()
}
}
- 4.更新預覽提供程序以傳遞數據數組中第一個
landmark
的坐標添吗。
import SwiftUI
import MapKit
struct MapView: UIViewRepresentable {
var coordinate: CLLocationCoordinate2D
func makeUIView(context: Context) -> MKMapView {
MKMapView(frame: .zero)
}
func updateUIView(_ view: MKMapView, context: Context) {
let span = MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02)
let region = MKCoordinateRegion(center: coordinate, span: span)
view.setRegion(region, animated: true)
}
}
struct MapView_Preview: PreviewProvider {
static var previews: some View {
MapView(coordinate: landmarkData[0].locationCoordinate)
}
}
5.在
LandmarkDetail.swift
中,將Landmark
類型的屬性添加到LandmarkDetail
類型中份名,作為其model數據碟联。6.更新預覽以使用
landmarkData
中的第一個lanmark
。7.完成將所需數據傳遞給您的自定義類型僵腺。
8.最后鲤孵,調用
navigationBarTitle(_:displayMode :)
方法,在顯示詳細視圖時為導航欄指定標題辰如。
- 9.在
SceneDelegate.swift
中普监,將rootViewController
的rootView
修改為為LandmarkList
,這樣首頁展示的就是列表頁了。
當我們在模擬器中獨立運行而不是預覽時鹰椒,我們的應用程序將以SceneDelegate
中定義的根視圖開始锡移。
- 10.在
LandmarkList.swift
中,將當前landmark
傳遞到目標LandmarkDetail
中漆际,關鍵代碼為NavigationButton(destination: LandmarkDetail(landmark: landmark))
淆珊。
import SwiftUI
struct LandmarkList : View {
var body: some View {
// 設置導航容器
NavigationView {
// 初始化一個類型TableView的view
List(landmarkData) { landmark in
// 點擊cell時,將當前`landmark`傳遞到目標`LandmarkDetail`中奸汇。
NavigationButton(destination: LandmarkDetail(landmark: landmark)) {
LandmarkRow(landmark: landmark)
}
}
// 顯示當前列表頁的導航標題
.navigationBarTitle(Text("Landmarks"))
}
}
}
#if DEBUG
struct LandmarkList_Previews : PreviewProvider {
static var previews: some View {
LandmarkList()
}
}
#endif
- 11.切換到實時預覽以查看從列表導航時詳細視圖顯示正確的標志
第8節(jié) 動態(tài)生成預覽
接下來施符,我們將向LandmarkList_Previews
預覽提供程序添加代碼,以顯示不同設備大小的列表視圖的預覽擂找。 默認情況下戳吝,預覽會以活動方案中設備的大小進行渲染。 我們可以通過調用previewDevice(_ :)
方法來更改預覽設備贯涎。
- 1.首先听哭,將當前列表預覽更改為以
iPhone SE
的大小呈現(xiàn)。
#if DEBUG
struct LandmarkList_Previews : PreviewProvider {
static var previews: some View {
LandmarkList()
// 以iPhone SE 設備的大小預覽畫布
.previewDevice(PreviewDevice(rawValue: "iPhone SE"))
}
}
#endif
我們可以提供Xcode
方案菜單中顯示的任何設備的名稱塘雳。
- 2.在畫布中添加對多個設備的預覽
在預覽的列表中陆盘,使用設備名稱數組作為數據,將LandmarkList
嵌入到ForEach
實例中败明。
ForEach
以與列表相同的方式對集合進行操作隘马,這意味著我們可以在任何可以使用子視圖的位置使用它,例如在堆棧妻顶,列表酸员,組等中。 當數據元素是簡單的值類型 - 就像在這里使用的字符串一樣 - 我們可以使用\ .self
作為標識符的關鍵路徑讳嘱。
- 3.使用
previewDisplayName(_ :)
修飾符將設備名稱添加為預覽的標簽幔嗦。
- 4.我們可以嘗試使用不同的設備來比較視圖的渲染,所有這些都來自畫布呢燥。
測試
- 1.除了List之外崭添,這些類型中的哪一個提供了集合中的動態(tài)視圖列表寓娩?
可選項:
1.Group
2.ForEath
3.UITableView
答案:2
- 2.我們可以從可識別元素集合中創(chuàng)建視圖列表叛氨。 我們使用什么方法來調整不符合可識別協(xié)議的元素集合?
可選項:
1.func map(_:)
2.func sorted(by:)
3.func identified(by:)
答案:3
- 3.用哪種類型來使
List
的行可以導航到另一個視圖棘伴?
可選項:
1.NavigationButton
2.UITableViewDelegate
3.NavigationView
答案: 1
- 4.哪些選項不是設置設備以預覽視圖的方法寞埠?
可選項:
- Change the simulator selected in the active scheme.
- Make a different choice in Canvas Settings in Xcode’s preferences.
- Specify one or more devices using the
previewDevice(_:)
method. - Connect your development device and click the Device Preview button.
答案:2