设置基本地标详细信息视图后,您需要为用户提供查看完整地标列表以及查看每个位置详细信息的方法。

您将创建可显示有关任何地标的信息的视图,并动态生成滚动列表,用户可以点按该列表以查看地标的详细视图。要微调UI,您将使用Xcode的画布以不同的设备大小呈现多个预览。

下载项目文件以开始构建此项目,并按照以下步骤操作。

预计的时间:35分

项目文件:https://docs-assets.developer.apple.com/published/ccd7eeefd0/BuildingListsAndNavigation.zip

Xcode 11 beta:https://developer.apple.com/download/

一、了解样本数据

在[SwiftUI教程]2、创建和组合视图中,您将信息硬编码到所有自定义视图中。在这里,您将学习将数据传递到自定义视图中以供显示。

首先下载入门项目并熟悉示例数据。

 

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_xcode

第1步

在Project导航器中,选择Models > Landmark.swift.

Landmark.swift声明一种Landmark结构,存储应用程序需要显示的所有地标信息,并从中导入一系列地标数据。landmarkData.json

1 import SwiftUI
 2 import CoreLocation
 3 
 4 struct Landmark: Hashable, Codable {
 5     var id: Int
 6     var name: String
 7     fileprivate var imageName: String
 8     fileprivate var coordinates: Coordinates
 9     var state: String
10     var park: String
11     var category: Category
12 
13     var locationCoordinate: CLLocationCoordinate2D {
14         CLLocationCoordinate2D(
15             latitude: coordinates.latitude,
16             longitude: coordinates.longitude)
17     }
18 
19     func image(forSize size: Int) -> Image {
20         ImageStore.shared.image(name: imageName, size: size)
21     }
22 
23     enum Category: String, CaseIterable, Codable, Hashable {
24         case featured = "Featured"
25         case lakes = "Lakes"
26         case rivers = "Rivers"
27     }
28 }
29 
30 struct Coordinates: Hashable, Codable {
31     var latitude: Double
32     var longitude: Double
33 }

第2步

在Project导航器中,选择Resources> landmarkData.json.

您将在本教程的其余部分以及随后的所有内容中使用此示例数据。

1 [
 2     {
 3         "name": "Turtle Rock",
 4         "category": "Featured",
 5         "city": "Twentynine Palms",
 6         "state": "California",
 7         "id": 1001,
 8         "park": "Joshua Tree National Park",
 9         "coordinates": {
10             "longitude": -116.166868,
11             "latitude": 34.011286
12         },
13         "imageName": "turtlerock"
14     },
15     {
16         "name": "Silver Salmon Creek",
17         "category": "Lakes",
18         "city": "Port Alsworth",
19         "state": "Alaska",
20         "id": 1002,
21         "park": "Lake Clark National Park and Preserve",
22         "coordinates": {
23             "longitude": -152.665167,
24             "latitude": 59.980167
25         },
26         "imageName": "silversalmoncreek"
27     },
28     ...
29 ]

第3步

请注意,现在可以命名创建和组合视图中的类型。ContentViewLandmarkDetail

您将在此以及以下每个教程中创建多个视图类型。

1 import SwiftUI
 2 
 3 struct LandmarkDetail: View {
 4     var body: some View {
 5         VStack {
 6             MapView()
 7                 .frame(height: 300)
 8 
 9             CircleImage()
10                 .offset(y: -130)
11                 .padding(.bottom, -130)
12 
13             VStack(alignment: .leading) {
14                 Text("Turtle Rock")
15                     .font(.title)
16 
17                 HStack(alignment: .top) {
18                     Text("Joshua Tree National Park")
19                         .font(.subheadline)
20                     Spacer()
21                     Text("California")
22                         .font(.subheadline)
23                 }
24             }
25             .padding()
26 
27             Spacer()
28         }
29     }
30 }
31 
32 struct LandmarkDetail_Preview: PreviewProvider {
33     static var previews: some View {
34         LandmarkDetail()
35     }
36 }

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_xcode_02

二、创建行视图

  您将在本教程中构建的第一个视图是用于显示每个地标的详细信息的行。 此行视图将信息存储在其显示的地标的属性中,以便一个视图可以显示任何地标。 稍后,您将多个行组合成一个地标列表。

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_xcode_03

第1步

创建一个名为的新SwiftUI视图。LandmarkRow.swift

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_xcode_04

第2步

如果预览不可见,请通过选择Editor > Editor and Canvas来显示画布,然后单击“Get Started”

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_Text_05

第3步

添加landmark为存储属性。LandmarkRow

添加landmark属性时,预览将停止工作,因为类型在初始化期间需要一个标记实例。LandmarkRow

1 import SwiftUI
 2 
 3 struct LandmarkRow: View {
 4     var landmark: Landmark
 5 
 6     var body: some View {
 7         Text("Hello World")
 8     }
 9 }
10 
11 struct LandmarkRow_Previews: PreviewProvider {
12     static var previews: some View {
13         LandmarkRow()
14     }
15 }

要修复预览,您需要修改预览提供程序。

第4步

在previewsstatic属性中,将landmark参数添加到初始值设定项,指定数组的第一个元素。LandmarkRow_PreviewsLandmarkRowlandmarkData

预览显示文本Hello World。修复后,您可以为行构建布局。

1 import SwiftUI
 2 
 3 struct LandmarkRow: View {
 4     var landmark: Landmark
 5 
 6     var body: some View {
 7         Text("Hello World")
 8     }
 9 }
10 
11 struct LandmarkRow_Previews: PreviewProvider {
12     static var previews: some View {
13         LandmarkRow(landmark: landmarkData[0])
14     }
15 }

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_移动开发_06

第5步

将现有文本视图嵌入到HStack。

1 import SwiftUI
 2 
 3 struct LandmarkRow: View {
 4     var landmark: Landmark
 5 
 6     var body: some View {
 7         HStack {
 8             Text("Hello World")
 9         }
10     }
11 }
12 
13 struct LandmarkRow_Previews: PreviewProvider {
14     static var previews: some View {
15         LandmarkRow(landmark: landmarkData[0])

第6步

修改文本视图以使用landmark属性name。

1 import SwiftUI
 2 
 3 struct LandmarkRow: View {
 4     var landmark: Landmark
 5 
 6     var body: some View {
 7         HStack {
 8             Text(landmark.name)
 9         }
10     }
11 }
12 
13 struct LandmarkRow_Previews: PreviewProvider {
14     static var previews: some View {
15         LandmarkRow(landmark: landmarkData[0])
16     }
17 }

第7步

通过在文本视图之前添加图像来完成行。

1 import SwiftUI
 2 
 3 struct LandmarkRow: View {
 4     var landmark: Landmark
 5 
 6     var body: some View {
 7         HStack {
 8             landmark.image(forSize: 50)
 9             Text(landmark.name)
10         }
11     }
12 }
13 
14 struct LandmarkRow_Previews: PreviewProvider {
15     static var previews: some View {
16         LandmarkRow(landmark: landmarkData[0])
17     }
18 }

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_ui_07

三、自定义行预览 

Xcode的画布自动识别并显示当前编辑器中符合协议的任何类型。预览提供程序返回一个或多个视图,其中包含用于配置大小和设备的选项。PreviewProvider

您可以从预览提供程序自定义返回的内容,以准确呈现对您最有帮助的预览。

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_xcode_08

 

 第1步 

在将参数更新为数组中的第二个元素。LandmarkRow_PreviewslandmarklandmarkData

预览立即更改以显示第二个样本地标而不是第一个。 

1 import SwiftUI
 2 
 3 struct LandmarkRow: View {
 4     var landmark: Landmark
 5 
 6     var body: some View {
 7         HStack {
 8             landmark.image(forSize: 50)
 9             Text(landmark.name)
10         }
11     }
12 }
13 
14 struct LandmarkRow_Previews: PreviewProvider {
15     static var previews: some View {
16         LandmarkRow(landmark: landmarkData[1])
17     }
18 }

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_ui_09

第2步

使用修饰符设置近似列表中行的大小。previewLayout(_:) 

1 import SwiftUI
 2 
 3 struct LandmarkRow: View {
 4     var landmark: Landmark
 5 
 6     var body: some View {
 7         HStack {
 8             landmark.image(forSize: 50)
 9             Text(landmark.name)
10         }
11     }
12 }
13 
14 struct LandmarkRow_Previews: PreviewProvider {
15     static var previews: some View {
16         LandmarkRow(landmark: landmarkData[1])
17             .previewLayout(.fixed(width: 300, height: 70))
18     }
19 }

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_ui_10

第3步

将返回的行换行Group,然后再将第一行添加回来。

Group是用于对视图内容进行分组的容器。Xcode将组的子视图渲染为画布中的单独预览。

1 import SwiftUI
 2 
 3 struct LandmarkRow: View {
 4     var landmark: Landmark
 5 
 6     var body: some View {
 7         HStack {
 8             landmark.image(forSize: 50)
 9             Text(landmark.name)
10         }
11     }
12 }
13 
14 struct LandmarkRow_Previews: PreviewProvider {
15     static var previews: some View {
16         Group {
17             LandmarkRow(landmark: landmarkData[0])
18                 .previewLayout(.fixed(width: 300, height: 70))
19             LandmarkRow(landmark: landmarkData[1])
20                 .previewLayout(.fixed(width: 300, height: 70))
21         }
22     }
23 }

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_移动开发_11

第4步

要简化代码,请将调用移至组的子声明外部。previewLayout(_:)

视图的子项继承视图的上下文设置,例如预览配置。

1 import SwiftUI
 2 
 3 struct LandmarkRow: View {
 4     var landmark: Landmark
 5 
 6     var body: some View {
 7         HStack {
 8             landmark.image(forSize: 50)
 9             Text(landmark.name)
10         }
11     }
12 }
13 
14 struct LandmarkRow_Previews: PreviewProvider {
15     static var previews: some View {
16         Group {
17             LandmarkRow(landmark: landmarkData[0])
18             LandmarkRow(landmark: landmarkData[1])
19         }
20         .previewLayout(.fixed(width: 300, height: 70))
21     }
22 }

您在预览提供程序中编写的代码仅更改Xcode在画布中显示的内容。由于该#if DEBUG指令,编译器会删除代码,因此它不会随您的应用程序一起提供。

四、创建地标列表

使用SwiftUI的 List 类型时,可以显示特定于平台的视图列表。列表的元素可以是静态的,就像您目前创建的堆栈的子视图一样,或者是动态生成的。您甚至可以混合静态和动态生成的视图。

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_Text_12

第1步

创建一个名为的新SwiftUI视图。LandmarkList.swift

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_Text_13

第2步

Text用a 替换默认视图List,并提供带有前两个标记的实例作为列表的子标记。LandmarkRow

预览显示以适合iOS的列表样式呈现的两个地标。

1 import SwiftUI
 2 
 3 struct LandmarkList: View {
 4     var body: some View {
 5         List {
 6             LandmarkRow(landmark: landmarkData[0])
 7             LandmarkRow(landmark: landmarkData[1])
 8         }
 9     }
10 }
11 
12 struct LandmarkList_Previews: PreviewProvider {
13     static var previews: some View {
14         LandmarkList()
15     }
16 }

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_在 SwiftUI 设置顶部的菜单栏_14

五、使列表动态化

您可以直接从集合中生成行,而不是单独指定列表的元素。

您可以通过传递数据集合以及为集合中的每个元素提供视图的闭包来创建显示集合元素的列表。该列表使用提供的闭包将集合中的每个元素转换为子视图。

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_在 SwiftUI 设置顶部的菜单栏_15

第1步

删除两个静态地标行,然后传递给初始化程序。landmarkDataList

列表使用可识别的数据。您可以通过以下两种方式之一来识别数据:通过identified(by:)使用唯一标识每个元素的属性的键路径调用方法,或者使数据类型符合Identifiable协议。

1 import SwiftUI
 2 
 3 struct LandmarkList: View {
 4     var body: some View {
 5         List(landmarkData.identified(by: \.id)) { landmark in
 6 
 7         }
 8     }
 9 }
10 
11 struct LandmarkList_Previews: PreviewProvider {
12     static var previews: some View {
13         LandmarkList()
14     }
15 }

第2步

通过从闭包返回a 来完成动态生成的列表。LandmarkRow

这为数组中的每个元素创建一个。LandmarkRowlandmarkData

1 import SwiftUI
 2 
 3 struct LandmarkList: View {
 4     var body: some View {
 5         List(landmarkData.identified(by: \.id)) { landmark in
 6             LandmarkRow(landmark: landmark)
 7         }
 8     }
 9 }
10 
11 struct LandmarkList_Previews: PreviewProvider {
12     static var previews: some View {
13         LandmarkList()
14     }
15 }

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_Text_16

接下来,您将List通过添加Identifiable对Landmark类型的一致性来简化代码。

第3步

切换到并声明符合协议。Landmark.swiftIdentifiable

由于该Landmark类型已经具有协议id所需的属性Identifiable,因此没有其他工作要做。

1 import SwiftUI
 2 import CoreLocation
 3 
 4 struct Landmark: Hashable, Codable, Identifiable {
 5     var id: Int
 6     var name: String
 7     fileprivate var imageName: String
 8     fileprivate var coordinates: Coordinates
 9     var state: String
10     var park: String
11     var category: Category
12 
13     var locationCoordinate: CLLocationCoordinate2D {
14         CLLocationCoordinate2D(
15             latitude: coordinates.latitude,
16             longitude: coordinates.longitude)
17     }
18 
19     func image(forSize size: Int) -> Image {
20         ImageStore.shared.image(name: imageName, size: size)
21     }
22 
23     enum Category: String, CaseIterable, Codable, Hashable {
24         case featured = "Featured"
25         case lakes = "Lakes"
26         case rivers = "Rivers"
27     }
28 }
29 
30 struct Coordinates: Hashable, Codable {
31     var latitude: Double
32     var longitude: Double
33 }

第4步

切换回并删除呼叫。LandmarkList.swiftidentified(by:)

从现在开始,您将能够Landmark直接使用元素集合。

1 import SwiftUI
 2 
 3 struct LandmarkList: View {
 4     var body: some View {
 5         List(landmarkData) { landmark in
 6             LandmarkRow(landmark: landmark)
 7         }
 8     }
 9 }
10 
11 struct LandmarkList_Previews: PreviewProvider {
12     static var previews: some View {
13         LandmarkList()
14     }
15 }

六、在列表和详细信息之间设置导航 

列表正确呈现,但您无法点击单个地标以查看该地标的详细信息页面。

您可以通过将导航功能嵌入到列表中,然后将每行嵌套在一个列表中来设置转换到目标视图,从而将导航功能添加到列表中。NavigationViewNavigationButton

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_Text_17

第1步

将动态生成的地标列表嵌入到。NavigationView

1 import SwiftUI
 2 
 3 struct LandmarkList: View {
 4     var body: some View {
 5         NavigationView {
 6             List(landmarkData) { landmark in
 7                 LandmarkRow(landmark: landmark)
 8             }
 9         }
10     }
11 }
12 
13 struct LandmarkList_Previews: PreviewProvider {
14     static var previews: some View {
15         LandmarkList()
16     }
17 }

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_移动开发_18

第2步

调用修改器方法以在显示列表时设置导航栏的标题。navigationBarTitle(_:)

1 import SwiftUI
 2 
 3 struct LandmarkList: View {
 4     var body: some View {
 5         NavigationView {
 6             List(landmarkData) { landmark in
 7                 LandmarkRow(landmark: landmark)
 8             }
 9             .navigationBarTitle(Text("Landmarks"))
10         }
11     }
12 }
13 
14 struct LandmarkList_Previews: PreviewProvider {
15     static var previews: some View {
16         LandmarkList()
17     }
18 }

第3步

在列表的闭包内,将返回的行包装在a中,将视图指定为目标。NavigationButtonLandmarkDetail

1 import SwiftUI
 2 
 3 struct LandmarkList: View {
 4     var body: some View {
 5         NavigationView {
 6             List(landmarkData) { landmark in
 7                 NavigationButton(destination: LandmarkDetail()) {
 8                     LandmarkRow(landmark: landmark)
 9                 }
10             }
11             .navigationBarTitle(Text("Landmarks"))
12         }
13     }
14 }
15 
16 struct LandmarkList_Previews: PreviewProvider {
17     static var previews: some View {
18         LandmarkList()
19     }
20 }

第4步

您可以通过切换到实时模式直接在预览中尝试导航。单击“实时预览”按钮,然后点击地标以访问详细信息页面。

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_在 SwiftUI 设置顶部的菜单栏_19

七、将数据传递到子视图

  该视图仍然使用硬编码的细节,以显示其标志性建筑。就像它所包含的类型和视图需要使用属性作为其数据的源。LandmarkDetailLandmarkRowLandmarkDetaillandmark

从子视图开始,您将转换,然后显示传入的数据,而不是硬编码每一行。CircleImageMapViewLandmarkDetail

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_Text_20

第1步

在添加存储的属性。CircleImage.swiftimageCircleImage

这是使用SwiftUI构建视图时的常见模式。您的自定义视图通常会为特定视图包装和封装一系列修改器。

1 import SwiftUI
 2 
 3 struct CircleImage: View {
 4     var image: Image
 5 
 6     var body: some View {
 7         image
 8             .clipShape(Circle())
 9             .overlay(Circle().stroke(Color.white, lineWidth: 4))
10             .shadow(radius: 10)
11     }
12 }
13 
14 struct CircleImage_Preview: PreviewProvider {
15     static var previews: some View {
16         CircleImage()
17     }
18 }

第2步

更新预览提供程序以传递Turtle Rock的图像。

1 import SwiftUI
 2 
 3 struct CircleImage: View {
 4     var image: Image
 5 
 6     var body: some View {
 7         image
 8             .clipShape(Circle())
 9             .overlay(Circle().stroke(Color.white, lineWidth: 4))
10             .shadow(radius: 10)
11     }
12 }
13 
14 struct CircleImage_Preview: PreviewProvider {
15     static var previews: some View {
16         CircleImage(image: Image("turtlerock"))
17     }
18 }

第3步

在,添加属性并转换代码以使用该属性而不是硬编码纬度和经度。MapView.swiftcoordinateMapView

1 import SwiftUI
 2 import MapKit
 3 
 4 struct MapView: UIViewRepresentable {
 5     var coordinate: CLLocationCoordinate2D
 6 
 7     func makeUIView(context: Context) -> MKMapView {
 8         MKMapView(frame: .zero)
 9     }
10 
11     func updateUIView(_ view: MKMapView, context: Context) {
12 
13         let span = MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02)
14         let region = MKCoordinateRegion(center: coordinate, span: span)
15         view.setRegion(region, animated: true)
16     }
17 }
18 
19 struct MapView_Preview: PreviewProvider {
20     static var previews: some View {
21         MapView()
22     }
23 }

第4步

更新预览提供程序以传递数据数组中第一个地标的坐标。

1 import SwiftUI
 2 import MapKit
 3 
 4 struct MapView: UIViewRepresentable {
 5     var coordinate: CLLocationCoordinate2D
 6 
 7     func makeUIView(context: Context) -> MKMapView {
 8         MKMapView(frame: .zero)
 9     }
10 
11     func updateUIView(_ view: MKMapView, context: Context) {
12         let span = MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02)
13         let region = MKCoordinateRegion(center: coordinate, span: span)
14         view.setRegion(region, animated: true)
15     }
16 }
17 
18 struct MapView_Preview: PreviewProvider {
19     static var previews: some View {
20         MapView(coordinate: landmarkData[0].locationCoordinate)
21     }
22 }

第5步

在,向该类型添加属性。LandmarkDetail.swiftLandmarkLandmarkDetail

1 import SwiftUI
 2 
 3 struct LandmarkDetail: View {
 4     var landmark: Landmark
 5 
 6     var body: some View {
 7         VStack {
 8             MapView()
 9                 .frame(height: 300)
10 
11             CircleImage()
12                 .offset(y: -130)
13                 .padding(.bottom, -130)
14 
15             VStack(alignment: .leading) {
16                 Text("Turtle Rock")
17                     .font(.title)
18 
19                 HStack(alignment: .top) {
20                     Text("Joshua Tree National Park")
21                         .font(.subheadline)
22                     Spacer()
23                     Text("California")
24                         .font(.subheadline)
25                 }
26             }
27             .padding()
28 
29             Spacer()
30         }
31     }
32 }
33 
34 struct LandmarkDetail_Preview: PreviewProvider {
35     static var previews: some View {
36         LandmarkDetail()
37     }
38 }

第6步

更新预览以使用第一个地标。landmarkData

1 import SwiftUI
 2 
 3 struct LandmarkDetail: View {
 4     var landmark: Landmark
 5 
 6     var body: some View {
 7         VStack {
 8             MapView()
 9                 .frame(height: 300)
10 
11             CircleImage()
12                 .offset(y: -130)
13                 .padding(.bottom, -130)
14 
15             VStack(alignment: .leading) {
16                 Text("Turtle Rock")
17                     .font(.title)
18 
19                 HStack(alignment: .top) {
20                     Text("Joshua Tree National Park")
21                         .font(.subheadline)
22                     Spacer()
23                     Text("California")
24                         .font(.subheadline)
25                 }
26             }
27             .padding()
28 
29             Spacer()
30         }
31     }
32 }
33 
34 struct LandmarkDetail_Preview: PreviewProvider {
35     static var previews: some View {
36         LandmarkDetail(landmark: landmarkData[0])
37     }
38 }

第7步

将所需数据传递给您的自定义类型。

1 import SwiftUI
 2 
 3 struct LandmarkDetail: View {
 4     var landmark: Landmark
 5 
 6     var body: some View {
 7         VStack {
 8             MapView(coordinate: landmark.locationCoordinate)
 9                 .frame(height: 300)
10 
11             CircleImage(image: landmark.image(forSize: 250))
12                 .offset(y: -130)
13                 .padding(.bottom, -130)
14 
15             VStack(alignment: .leading) {
16                 Text(landmark.name)
17                     .font(.title)
18 
19                 HStack(alignment: .top) {
20                     Text(landmark.park)
21                         .font(.subheadline)
22                     Spacer()
23                     Text(landmark.state)
24                         .font(.subheadline)
25                 }
26             }
27             .padding()
28 
29             Spacer()
30         }
31     }
32 }
33 
34 struct LandmarkDetail_Preview: PreviewProvider {
35     static var previews: some View {
36         LandmarkDetail(landmark: landmarkData[0])
37     }
38 }

第8步

最后,在显示详细视图时,调用修改器为导航栏指定标题。navigationBarTitle(_:displayMode:)

1 import SwiftUI
 2 
 3 struct LandmarkDetail: View {
 4     var landmark: Landmark
 5 
 6     var body: some View {
 7         VStack {
 8             MapView(coordinate: landmark.locationCoordinate)
 9                 .frame(height: 300)
10 
11             CircleImage(image: landmark.image(forSize: 250))
12                 .offset(y: -130)
13                 .padding(.bottom, -130)
14 
15             VStack(alignment: .leading) {
16                 Text(landmark.name)
17                     .font(.title)
18 
19                 HStack(alignment: .top) {
20                     Text(landmark.park)
21                         .font(.subheadline)
22                     Spacer()
23                     Text(landmark.state)
24                         .font(.subheadline)
25                 }
26             }
27             .padding()
28 
29             Spacer()
30         }
31         .navigationBarTitle(Text(landmark.name), displayMode: .inline)
32     }
33 }
34 
35 struct LandmarkDetail_Preview: PreviewProvider {
36     static var previews: some View {
37         LandmarkDetail(landmark: landmarkData[0])
38     }
39 }

第9步

在,切换应用程序的根视图。SceneDelegate.swiftLandmarkList

您的应用程序以在模拟器中独立运行时定义的根视图而不是预览开始。SceneDelegate

1 import UIKit
 2 import SwiftUI
 3 
 4 class SceneDelegate: UIResponder, UIWindowSceneDelegate {
 5     var window: UIWindow?
 6 
 7     func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
 8         // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
 9         // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
10         // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
11 
12         // Use a UIHostingController as window root view controller
13         let window = UIWindow(frame: UIScreen.main.bounds)
14         window.rootViewController = UIHostingController(rootView: LandmarkList())
15         self.window = window
16         window.makeKeyAndVisible()
17     }
18 
19     // ...
20 }

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_xcode_21

第10步

在,将当前地标传递到目的地。LandmarkList.swiftLandmarkDetail

 

1 import SwiftUI
 2 
 3 struct LandmarkList: View {
 4     var body: some View {
 5         NavigationView {
 6             List(landmarkData) { landmark in
 7                 NavigationButton(destination: LandmarkDetail(landmark: landmark)) {
 8                     LandmarkRow(landmark: landmark)
 9                 }
10             }
11             .navigationBarTitle(Text("Landmarks"))
12         }
13     }
14 }
15 
16 struct LandmarkList_Previews: PreviewProvider {
17     static var previews: some View {
18         LandmarkList()
19     }
20 }

第11步

从列表导航时,切换到实时预览以查看详细视图显示正确的标记。

 

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_在 SwiftUI 设置顶部的菜单栏_22

八、动态生成预览

接下来,您将向预览提供程序添加代码,以便以不同的设备大小呈现列表视图的预览。默认情况下,预览会以活动方案中设备的大小进行渲染。您可以通过调用修改器方法来更改预览设备。LandmarkList_PreviewspreviewDevice(_:)

 

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_在 SwiftUI 设置顶部的菜单栏_23

第1步

首先,将当前列表预览更改为以iPhone SE的大小呈现。

您可以提供Xcode方案菜单中显示的任何设备的名称。

1 import SwiftUI
 2 
 3 struct LandmarkList: View {
 4     var body: some View {
 5         NavigationView {
 6             List(landmarkData) { landmark in
 7                 NavigationButton(destination: LandmarkDetail(landmark: landmark)) {
 8                     LandmarkRow(landmark: landmark)
 9                 }
10             }
11             .navigationBarTitle(Text("Landmarks"))
12         }
13     }
14 }
15 
16 struct LandmarkList_Previews: PreviewProvider {
17     static var previews: some View {
18         LandmarkList()
19             .previewDevice(PreviewDevice(rawValue: "iPhone XS"))
20     }
21 }

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_xcode_24

第2步

在列表预览中,使用设备名称数组作为数据嵌入实例中。LandmarkListForEach

ForEach以与列表相同的方式对集合进行操作,这意味着您可以在任何可以使用子视图的位置使用它,例如在堆栈,列表,组等中。当数据元素是简单的值类型(如您在此处使用的字符串)时,您可以将其\.self用作标识符的关键路径。

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_Text_25

第3步

使用修改器将设备名称添加为预览的标签。previewDisplayName(_:)

1 import SwiftUI
 2 
 3 struct LandmarkList: View {
 4     var body: some View {
 5         NavigationView {
 6             List(landmarkData) { landmark in
 7                 NavigationButton(destination: LandmarkDetail(landmark: landmark)) {
 8                     LandmarkRow(landmark: landmark)
 9                 }
10             }
11             .navigationBarTitle(Text("Landmarks"))
12         }
13     }
14 }
15 
16 struct LandmarkList_Previews: PreviewProvider {
17     static var previews: some View {
18         ForEach(["iPhone SE", "iPhone XS Max"].identified(by: \.self)) { deviceName in
19             LandmarkList()
20                 .previewDevice(PreviewDevice(rawValue: deviceName))
21                 .previewDisplayName(deviceName)
22         }
23     }
24 }

第4步

您可以尝试使用不同的设备来比较视图的渲染,所有这些都来自画布。

在 SwiftUI 设置顶部的菜单栏 swiftui导航栏_xcode_26