Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Building Lists and Navigation

Willie edited this page Feb 10, 2020 · 12 revisions

构建列表与导航

完成了基础的地标详情视图后,我们需要为用户提供查看完整地标列表,以及查看每个地标详情的方法。

在本文中,我们将会创建可显示任何地标信息的视图 ,并动态生成滚动列表,用户可以点按该列表以查看地标的详细视图。另外,我们还将使用 Xcode 的画布来显示不同设备的大小,以此来微调 UI。

下载项目文件并按照以下步骤操作。

  • 预计完成时间:35 分钟
  • 初始项目文件:下载

1. 了解样本数据

上一个教程 中,我们把数据硬编码到了所有自定义视图中。在本文中,我们来学习如何将数据传递到自定义视图中并显示。

下载初始项目并熟悉一下样本数据。

1.1 在项目导航器中,选择 Models > Landmark.swift

Landmark.swift 声明了一个 Landmark 结构体,用来存储 app 需要显示的所有地标数据,并从 landmarkData.json 导入一组地标数据。

Landmark.swift

import SwiftUI
import CoreLocation
struct Landmark: Hashable, Codable {
 var id: Int
 var name: String
 fileprivate var imageName: String
 fileprivate var coordinates: Coordinates
 var state: String
 var park: String
 var category: Category
 var locationCoordinate: CLLocationCoordinate2D {
 CLLocationCoordinate2D(
 latitude: coordinates.latitude,
 longitude: coordinates.longitude)
 }
 enum Category: String, CaseIterable, Codable, Hashable {
 case featured = "Featured"
 case lakes = "Lakes"
 case rivers = "Rivers"
 }
}
extension Landmark {
 var image: Image {
 ImageStore.shared.image(name: imageName)
 }
}
struct Coordinates: Hashable, Codable {
 var latitude: Double
 var longitude: Double
}

1.2 在项目导航器中,选择 Resources > landmarkData.json

我们会在本教程的剩余部分以及随后的所有内容中使用此样本数据。

landmarkData.json

[
 {
 "name": "Turtle Rock",
 "category": "Featured",
 "city": "Twentynine Palms",
 "state": "California",
 "id": 1001,
 "park": "Joshua Tree National Park",
 "coordinates": {
 "longitude": -116.166868,
 "latitude": 34.011286
 },
 "imageName": "turtlerock"
 },
 {
 "name": "Silver Salmon Creek",
 "category": "Lakes",
 "city": "Port Alsworth",
 "state": "Alaska",
 "id": 1002,
 "park": "Lake Clark National Park and Preserve",
 "coordinates": {
 "longitude": -152.665167,
 "latitude": 59.980167
 },
 "imageName": "silversalmoncreek"
 },
 ...
]

1.3 需要注意的是, 上一个教程 中的 ContentView 类型现在更名为 LandmarkDetail

接下来我们还会创建多个视图类型。

LandmarkDetail.swift

import SwiftUI
struct LandmarkDetail: View {
 var body: some View {
 VStack {
 MapView()
 .frame(height: 300)
 CircleImage()
 .offset(y: -130)
 .padding(.bottom, -130)
 VStack(alignment: .leading) {
 Text("Turtle Rock")
 .font(.title)
 HStack(alignment: .top) {
 Text("Joshua Tree National Park")
 .font(.subheadline)
 Spacer()
 Text("California")
 .font(.subheadline)
 }
 }
 .padding()
 Spacer()
 }
 }
}
struct LandmarkDetail_Preview: PreviewProvider {
 static var previews: some View {
 LandmarkDetail()
 }
}

2. 创建行视图

我们在本文中构建的第一个视图是用于显示每个地标详情的行视图。 行视图将地标数据存储在其 landmark 属性中,这样一个行视图就可以显示任何地标。稍后我们会把多个行视图组合成一个地标列表。

2.1 创建一个新的 SwiftUI 视图,命名为 LandmarkRow.swift

2.2 如果预览没有显示,请选择 Editor > Editor and Canvas , 然后单击 Get Started

2.3 给 LandmarkRow 添加一个存储属性 landmark

当你添加 landmark 属性时,预览会停止工作,因为 LandmarkRow 类型在初始化时需要一个 landmark 实例。

LandmarkRow.swift

import SwiftUI
struct LandmarkRow: View {
 var landmark: Landmark
 var body: some View {
 Text("Hello World")
 }
}
struct LandmarkRow_Previews: PreviewProvider {
 static var previews: some View {
 LandmarkRow()
 }
}

为了恢复预览,我们需要修改 PreviewProvider

2.4 在 LandmarkRow_Previews 的静态属性 previews 中,给 LandmarkRow 的初始化方法添加 landmark 参数,并将 landmarkData 数组的第一个元素赋值给 landmark 参数。

这时预览就会显示 Hello World 的文字。

LandmarkRow.swift

import SwiftUI
struct LandmarkRow: View {
 var landmark: Landmark
 var body: some View {
 Text("Hello World")
 }
}
struct LandmarkRow_Previews: PreviewProvider {
 static var previews: some View {
 //
 LandmarkRow(landmark: landmarkData[0])
 //
 }
}

恢复预览后,我们就可以构建行视图的布局了。

2.5 把现有的文字视图嵌套到一个 HStack 中。

LandmarkRow.swift

import SwiftUI
struct LandmarkRow: View {
 var landmark: Landmark
 var body: some View {
 //
 HStack {
 Text("Hello World")
 }
 //
 }
}
struct LandmarkRow_Previews: PreviewProvider {
 static var previews: some View {
 LandmarkRow(landmark: landmarkData[0])
 }
}

2.6 将文字视图的内容修改成 landmark.name

LandmarkRow.swift

import SwiftUI
struct LandmarkRow: View {
 var landmark: Landmark
 var body: some View {
 HStack {
 //
 Text(landmark.name)
 //
 }
 }
}
struct LandmarkRow_Previews: PreviewProvider {
 static var previews: some View {
 LandmarkRow(landmark: landmarkData[0])
 }
}

2.7 在文字视图前添加一个图片来完成行视图。

LandmarkRow.swift

import SwiftUI
struct LandmarkRow: View {
 var landmark: Landmark
 var body: some View {
 HStack {
 //
 landmark.image
 .resizable()
 .frame(width: 50, height: 50)
 //
 Text(landmark.name)
 Spacer()
 }
 }
}
struct LandmarkRow_Previews: PreviewProvider {
 static var previews: some View {
 LandmarkRow(landmark: landmarkData[0])
 }
}

3. 自定义行视图的预览

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

通过自定义 preview provider 的返回值,我们可以让预览来显示需要的内容。

3.1 在 LandmarkRow_Previews 中,把 landmark 的参数改成 landmarkData 数组的第二个元素。

预览会立即从第一个元素切换到第二个元素的显示。

LandmarkRow.swift

import SwiftUI
struct LandmarkRow: View {
 var landmark: Landmark
 var body: some View {
 HStack {
 landmark.image
 .resizable()
 .frame(width: 50, height: 50)
 Text(landmark.name)
 Spacer()
 }
 }
}
struct LandmarkRow_Previews: PreviewProvider {
 static var previews: some View {
 //
 LandmarkRow(landmark: landmarkData[1])
 //
 }
}

3.2 用 previewLayout(_:) 修饰符设置行视图在列表中的预览大小。

LandmarkRow.swift

import SwiftUI
struct LandmarkRow: View {
 var landmark: Landmark
 var body: some View {
 HStack {
 landmark.image
 .resizable()
 .frame(width: 50, height: 50)
 Text(landmark.name)
 Spacer()
 }
 }
}
struct LandmarkRow_Previews: PreviewProvider {
 static var previews: some View {
 LandmarkRow(landmark: landmarkData[1])
 //
 .previewLayout(.fixed(width: 300, height: 70))
 //
 }
}

我们可以在 preview provider 中使用 Group 来返回多个预览。

3.3 把返回的行视图包装到一个 Group 中,并且把第一个行视图添加回来。

Group 是一个组合视图的容器。 Xcode 会在画布中把 Group 的子视图当作各自的预览渲染出来。

LandmarkRow.swift

import SwiftUI
struct LandmarkRow: View {
 var landmark: Landmark
 var body: some View {
 HStack {
 landmark.image
 .resizable()
 .frame(width: 50, height: 50)
 Text(landmark.name)
 Spacer()
 }
 }
}
struct LandmarkRow_Previews: PreviewProvider {
 static var previews: some View {
 //
 Group {
 LandmarkRow(landmark: landmarkData[0])
 .previewLayout(.fixed(width: 300, height: 70))
 LandmarkRow(landmark: landmarkData[1])
 .previewLayout(.fixed(width: 300, height: 70))
 }
 //
 }
}

previewLayout(_:) 的调用移到 group 声明的外面来精简代码。

一个视图的子项会继承视图的上下文设置,比如这里的预览设置。

LandmarkRow.swift

import SwiftUI
struct LandmarkRow: View {
 var landmark: Landmark
 var body: some View {
 HStack {
 landmark.image
 .resizable()
 .frame(width: 50, height: 50)
 Text(landmark.name)
 Spacer()
 }
 }
}
struct LandmarkRow_Previews: PreviewProvider {
 static var previews: some View {
 Group {
 LandmarkRow(landmark: landmarkData[0])
 LandmarkRow(landmark: landmarkData[1])
 }
 //
 .previewLayout(.fixed(width: 300, height: 70))
 //
 }
}

preview provider 中编写的代码只会改变 Xcode 在画布中的显示。

4. 创建地标列表

使用 SwiftUIList 类型可以显示平台特有的列表视图。列表的元素可以是静态的,就像我们创建的 stacks 的子视图一样;也可以是动态生成的。甚至可以把静态和动态生成的视图混合在一起。

4.1 创建一个新的 SwiftUI 视图,命名为 LandmarkList.swift

4.2 把默认的文字视图换成 List ,然后传入两个包含头两个地标数据的 LandmarkRow 对象,作为 List 的子项。

预览会以适合 iOS 样式的列表来显示这两个地标。

LandmarkList.swift

import SwiftUI
struct LandmarkList: View {
 var body: some View {
 //
 List {
 LandmarkRow(landmark: landmarkData[0])
 LandmarkRow(landmark: landmarkData[1])
 }
 //
 }
}
struct LandmarkList_Previews: PreviewProvider {
 static var previews: some View {
 LandmarkList()
 }
}

5. 动态化列表

相比于给 list 指定单个元素,我们还可以直接从集合中生成行视图。

通过传递一个数据集合和一个给每个元素提供视图的闭包来让 list 显示集合的元素。 list 通过传递的闭包来把每个集合中的元素转换成子视图。

5.1 移除现有的两个静态地标行视图,然后给 List 的初始化方法传递 landmarkData

list 使用 identifiable 的数据,我们可以使用以下两个方法之一来让数据变成 identifiable :使用 key path 属性来唯一标识每个元素,或者让数据类型遵循 Identifiable 协议。

LandmarkList.swift

import SwiftUI
struct LandmarkList: View {
 var body: some View {
 //
 List(landmarkData, id: \.id) { landmark in
 }
 //
 }
}
struct LandmarkList_Previews: PreviewProvider {
 static var previews: some View {
 LandmarkList()
 }
}

5.2 在闭包中返回 LandmarkRow ,我们就完成了自动生成内容的 list

这会给 landmarkData 数组中的每一个元素创建一个 LandmarkRow

LandmarkList.swift

import SwiftUI
struct LandmarkList: View {
 var body: some View {
 List(landmarkData, id: \.id) { landmark in
 //
 LandmarkRow(landmark: landmark)
 //
 }
 }
}
struct LandmarkList_Previews: PreviewProvider {
 static var previews: some View {
 LandmarkList()
 }
}

接下来,我们通过给 Landmark 类型添加遵循 Identifiable 的声明来简化代码。

5.3 切换到 Landmark.swift ,声明遵循 Identifiable 协议。

Landmark 类型声明了 Identifiable 协议需要的 id 属性后,我们就完成了对 Landmark 的修改。

Landmark.swift

import SwiftUI
import CoreLocation
//
struct Landmark: Hashable, Codable, Identifiable {
//
 var id: Int
 var name: String
 fileprivate var imageName: String
 fileprivate var coordinates: Coordinates
 var state: String
 var park: String
 var category: Category
 var locationCoordinate: CLLocationCoordinate2D {
 CLLocationCoordinate2D(
 latitude: coordinates.latitude,
 longitude: coordinates.longitude)
 }
 enum Category: String, CaseIterable, Codable, Hashable {
 case featured = "Featured"
 case lakes = "Lakes"
 case rivers = "Rivers"
 }
}
extension Landmark {
 var image: Image {
 ImageStore.shared.image(name: imageName)
 }
}
struct Coordinates: Hashable, Codable {
 var latitude: Double
 var longitude: Double
}

5.4 切回 LandmarkList,删除 id 参数。

从现在开始,我们可以直接使用 Landmark 元素的集合。

LandmarkList.swift

import SwiftUI
struct LandmarkList: View {
 var body: some View {
 //
 List(landmarkData) { landmark in
 //
 LandmarkRow(landmark: landmark)
 }
 }
}
struct LandmarkList_Previews: PreviewProvider {
 static var previews: some View {
 LandmarkList()
 }
}

6. 在列表和详情之间设置导航

虽然列表已经能显示了,但是我们还不能通过点击单个地标来查看地标详情页面。

list 嵌入一个 NavigationView 中,并把每个行视图嵌套在一个 NavigationButton 中来设置到目标视图的转场,这样 list 就具有了导航功能。

6.1 把自动创建地标的 list 嵌入到一个 NavigationView 中。

LandmarkList.swift

import SwiftUI
struct LandmarkList: View {
 var body: some View {
 //
 NavigationView {
 List(landmarkData) { landmark in
 LandmarkRow(landmark: landmark)
 }
 }
 //
 }
}
struct LandmarkList_Previews: PreviewProvider {
 static var previews: some View {
 LandmarkList()
 }
}

调用 navigationBarTitle(_:) 修饰符来设置 list 显示时导航栏的标题。

LandmarkList.swift

import SwiftUI
struct LandmarkList: View {
 var body: some View {
 NavigationView {
 List(landmarkData) { landmark in
 LandmarkRow(landmark: landmark)
 }
 //
 .navigationBarTitle(Text("Landmarks"))
 //
 }
 }
}
struct LandmarkList_Previews: PreviewProvider {
 static var previews: some View {
 LandmarkList()
 }
}

6.3 在 list 的闭包中,把返回的行视图包装在一个 NavigationButton 中,并把 LandmarkDetail 视图作为目标。

LandmarkList.swift

import SwiftUI
struct LandmarkList: View {
 var body: some View {
 NavigationView {
 List(landmarkData) { landmark in
 //
 NavigationLink(destination: LandmarkDetail()) {
 LandmarkRow(landmark: landmark)
 }
 //
 }
 .navigationBarTitle(Text("Landmarks"))
 }
 }
}
struct LandmarkList_Previews: PreviewProvider {
 static var previews: some View {
 LandmarkList()
 }
}

6.4 切换到实时模式后可以直接在预览中尝试导航功能。单击 Live Preview 按钮,然后点击地标来访问详情页面。

7. 给子视图传递数据

LandmarkDetail 现在依然使用硬编码的数据来显示地标。像 LandmarkRow 一样,LandmarkDetail 类型和它组合的其他视图都需要一个 landmark 属性作为它们的数据源。

在开始子视图的内容时,我们会把 CircleImageMapViewLandmarkDetail 的显示从硬编码改为传入的数据。

7.1 在 CircleImage.swif 中,添加存储属性 image

这是使用 SwiftUI 构建视图时的常见模式。我们的自定义视图通常会为特定视图包装和封装一些修饰符。

CircleImage.swift

import SwiftUI
struct CircleImage: View {
 //
 var image: Image
 //
 var body: some View {
 //
 image
 //
 .clipShape(Circle())
 .overlay(Circle().stroke(Color.white, lineWidth: 4))
 .shadow(radius: 10)
 }
}
struct CircleImage_Preview: PreviewProvider {
 static var previews: some View {
 CircleImage()
 }
}

7.2 更新 preview provider ,传递一个 Turtle Rock 的图片。

CircleImage.swift

import SwiftUI
struct CircleImage: View {
 var image: Image
 var body: some View {
 image
 .clipShape(Circle())
 .overlay(Circle().stroke(Color.white, lineWidth: 4))
 .shadow(radius: 10)
 }
}
struct CircleImage_Preview: PreviewProvider {
 static var previews: some View {
 //
 CircleImage(image: Image("turtlerock"))
 //
 }
}

7.3 在 MapView.swift 中,给 MapView 添加一个 coordinate 属性,然后把经纬度的硬编码换成使用这个属性。

MapView.swift

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()
 }
}

7.4 更新 preview provider ,传递数据数组中第一个地标的坐标。

MapView.swift

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)
 //
 }
}

7.5 在 LandmarkDetail.swift 中,给 LandmarkDetail 类型添加 landmark 属性。

LandmarkDetail.swift

import SwiftUI
struct LandmarkDetail: View {
 //
 var landmark: Landmark
 //
 var body: some View {
 VStack {
 MapView()
 .frame(height: 300)
 CircleImage()
 .offset(y: -130)
 .padding(.bottom, -130)
 VStack(alignment: .leading) {
 Text("Turtle Rock")
 .font(.title)
 HStack(alignment: .top) {
 Text("Joshua Tree National Park")
 .font(.subheadline)
 Spacer()
 Text("California")
 .font(.subheadline)
 }
 }
 .padding()
 Spacer()
 }
 }
}
struct LandmarkDetail_Preview: PreviewProvider {
 static var previews: some View {
 LandmarkDetail()
 }
}

7.6 更新 preview provider ,使用 landmarkData 中的第一个地标。

LandmarkDetail.swift

import SwiftUI
struct LandmarkDetail: View {
 var landmark: Landmark
 var body: some View {
 VStack {
 MapView()
 .frame(height: 300)
 CircleImage()
 .offset(y: -130)
 .padding(.bottom, -130)
 VStack(alignment: .leading) {
 Text("Turtle Rock")
 .font(.title)
 HStack(alignment: .top) {
 Text("Joshua Tree National Park")
 .font(.subheadline)
 Spacer()
 Text("California")
 .font(.subheadline)
 }
 }
 .padding()
 Spacer()
 }
 }
}
struct LandmarkDetail_Preview: PreviewProvider {
 static var previews: some View {
 //
 LandmarkDetail(landmark: landmarkData[0])
 //
 }
}

7.7 将所需数据传递给我们的自定义类型。

LandmarkDetail.swift

import SwiftUI
struct LandmarkDetail: View {
 var landmark: Landmark
 var body: some View {
 VStack {
 //
 MapView(coordinate: landmark.locationCoordinate)
 //
 .frame(height: 300)
 //
 CircleImage(image: landmark.image)
 //
 .offset(y: -130)
 .padding(.bottom, -130)
 VStack(alignment: .leading) {
 //
 Text(landmark.name)
 //
 .font(.title)
 HStack(alignment: .top) {
 //
 Text(landmark.park)
 //
 .font(.subheadline)
 Spacer()
 //
 Text(landmark.state)
 //
 .font(.subheadline)
 }
 }
 .padding()
 Spacer()
 }
 }
}
struct LandmarkDetail_Preview: PreviewProvider {
 static var previews: some View {
 LandmarkDetail(landmark: landmarkData[0])
 }
}

7.8 最后,调用 navigationBarTitle(_:displayMode:) 修饰符,给导航栏添加显示详情视图时的标题。

LandmarkDetail.swift

import SwiftUI
struct LandmarkDetail: View {
 var landmark: Landmark
 var body: some View {
 VStack {
 MapView(coordinate: landmark.locationCoordinate)
 .frame(height: 300)
 CircleImage(image: landmark.image)
 .offset(y: -130)
 .padding(.bottom, -130)
 VStack(alignment: .leading) {
 Text(landmark.name)
 .font(.title)
 HStack(alignment: .top) {
 Text(landmark.park)
 .font(.subheadline)
 Spacer()
 Text(landmark.state)
 .font(.subheadline)
 }
 }
 .padding()
 Spacer()
 }
 //
 .navigationBarTitle(Text(landmark.name), displayMode: .inline)
 //
 }
}
struct LandmarkDetail_Preview: PreviewProvider {
 static var previews: some View {
 LandmarkDetail(landmark: landmarkData[0])
 }
}

7.9 在 SceneDelegate.swift 中,把 app 的 rootView 改成 LandmarkList

当我们不使用预览而是在模拟器中独立运行 app 时,app 会以 SceneDelegate 中定义的 rootView 开始显示。

SceneDelegate.swift

import UIKit
import SwiftUI
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
 var window: UIWindow?
 func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
 // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
 // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
 // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
 // Use a UIHostingController as window root view controller
 if let windowScene = scene as? UIWindowScene {
 let window = UIWindow(windowScene: windowScene)
 //
 window.rootViewController = UIHostingController(rootView: LandmarkList())
 //
 self.window = window
 window.makeKeyAndVisible()
 }
 }
 // ...
}

7.10 在 LandmarkList.swift 中,给目标 LandmarkDetail 传递当前的地标。

LandmarkList.swift

import SwiftUI
struct LandmarkList: View {
 var body: some View {
 NavigationView {
 List(landmarkData) { landmark in
 //
 NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
 //
 LandmarkRow(landmark: landmark)
 }
 }
 .navigationBarTitle(Text("Landmarks"))
 }
 }
}
struct LandmarkList_Previews: PreviewProvider {
 static var previews: some View {
 LandmarkList()
 }
}

7.11 切换到实时预览,可以查看从列表导航到正确的地标详情视图了。

8. 动态生成预览

接下来,我们会在 LandmarkList_Previews 中添加代码以在不同的设备尺寸上渲染列表。默认情况下,预览会以当前的 scheme 中设备的大小进行渲染。我们可以通过调用 previewDevice(_:) 修饰符来改变预览设备。

8.1 首先,改变当前 list 的预览来显示 iPhone SE 的尺寸。

我们可以输入任何 Xcode scheme 菜单中显示的设备名称。

LandmarkList.swift

import SwiftUI
struct LandmarkList: View {
 var body: some View {
 NavigationView {
 List(landmarkData) { landmark in
 NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
 LandmarkRow(landmark: landmark)
 }
 }
 .navigationBarTitle(Text("Landmarks"))
 }
 }
}
struct LandmarkList_Previews: PreviewProvider {
 static var previews: some View {
 LandmarkList()
 //
 .previewDevice(PreviewDevice(rawValue: "iPhone SE"))
 //
 }
}

8.2 在 list 预览中用设备名称数组作为数据,将 LandmarkList 嵌入到 ForEach 实例中。

ForEach 以与 list 相同的方式对集合进行操作,这样我们就可以在任何可以使用子视图的地方使用它,比如 stacks , lists ,groups 等。当数据元素像这里使用的字符串一样是简单的值类型时,我们可以使用 \.self 作为标识符的 key path

LandmarkList.swift

import SwiftUI
struct LandmarkList: View {
 var body: some View {
 NavigationView {
 List(landmarkData) { landmark in
 NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
 LandmarkRow(landmark: landmark)
 }
 }
 .navigationBarTitle(Text("Landmarks"))
 }
 }
}
struct LandmarkList_Previews: PreviewProvider {
 static var previews: some View {
 //
 ForEach(["iPhone SE", "iPhone XS Max"], id: \.self) { deviceName in
 LandmarkList()
 .previewDevice(PreviewDevice(rawValue: deviceName))
 }
 //
 }
}

8.3 使用 previewDisplayName(_:) 修饰符把设备名称作为 labels 添加到预览中。

LandmarkList.swift

import SwiftUI
struct LandmarkList: View {
 var body: some View {
 NavigationView {
 List(landmarkData) { landmark in
 NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
 LandmarkRow(landmark: landmark)
 }
 }
 .navigationBarTitle(Text("Landmarks"))
 }
 }
}
struct LandmarkList_Previews: PreviewProvider {
 static var previews: some View {
 ForEach(["iPhone SE", "iPhone XS Max"], id: \.self) { deviceName in
 LandmarkList()
 .previewDevice(PreviewDevice(rawValue: deviceName))
 //
 .previewDisplayName(deviceName)
 //
 }
 }
}

8.4 我们可以在画布中体验不同的设备,对比它们在渲染视图时的差异。

Clone this wiki locally

AltStyle によって変換されたページ (->オリジナル) /