[译] SwiftUI 官方教程 (三)

因为 API 变更,此文章部份内容已失效,最新完整中文教程及代码请查看 github.com/WillieWangW…git

微信技术群

SwiftUI 表明将来构建 App 的方向,欢迎加群一块儿交流技术,解决问题。github

构建列表与导航

完成了基础的地标详情 view 后,咱们须要为用户提供查看完整地标列表,以及查看每一个地标详情的方法。json

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

下载项目文件并按照如下步骤操做。swift

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

1. 了解样本数据

上一个教程 中,咱们把数据硬编码到了全部自定义 view 中。在本文中,咱们来学习如何将数据传递到自定义 view 中并显示。数组

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

1.1 在 Project navigator 中,选择 Models > Landmark.swift微信

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

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

    func image(forSize size: Int) -> Image {
        ImageStore.shared.image(name: imageName, size: size)
    }

    enum Category: String, CaseIterable, Codable, Hashable {
        case featured = "Featured"
        case lakes = "Lakes"
        case rivers = "Rivers"
    }
}

struct Coordinates: Hashable, Codable {
    var latitude: Double
    var longitude: Double
}
复制代码

1.2 在 Project navigator 中,选择 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

接下来咱们还会建立多个 view 类型。

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. 建立 Row View

咱们在本文中构建的第一个 view 是用于显示每一个地标详情的 rowrow 将地标数据存储在 landmark 属性中,这样一个 row 就能够显示任何地标。稍后咱们会把多个 row 组合成一个地标列表。

2.1 建立一个新的 SwiftUI view,命名为 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])
    }
}
复制代码

恢复预览后,咱们就能够构建 row 的布局了。

2.5 把现有的 text view 嵌套到一个 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 将 text view 的内容修改为 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 在 text view 前添加一个图片来完成 row

LandmarkRow.swift

import SwiftUI

struct LandmarkRow: View {
    var landmark: Landmark

    var body: some View {
        HStack {
            landmark.image(forSize: 50)
            Text(landmark.name)
        }
    }
}

struct LandmarkRow_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkRow(landmark: landmarkData[0])
    }
}
复制代码

3. 自定义 Row 的预览

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

经过自定义 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(forSize: 50)
            Text(landmark.name)
        }
    }
}

struct LandmarkRow_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkRow(landmark: landmarkData[1])
    }
}
复制代码

3.2 用 previewLayout(_:) 方法设置 row 在列表中的大概大小。

LandmarkRow.swift

import SwiftUI

struct LandmarkRow: View {
    var landmark: Landmark

    var body: some View {
        HStack {
            landmark.image(forSize: 50)
            Text(landmark.name)
        }
    }
}

struct LandmarkRow_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkRow(landmark: landmarkData[1])
            .previewLayout(.fixed(width: 300, height: 70))
    }
}
复制代码

咱们能够在 preview provider 中使用 Group 来返回多个预览。

3.3 把返回的 row 包装到一个 Group 中,而且把第一个 row 添加回来。

Group 是一个组合 view 的容器。 Xcode 会在 canvas 中把 Group 的子 view 做为分开的预览渲染出来。

LandmarkRow.swift

import SwiftUI

struct LandmarkRow: View {
    var landmark: Landmark

    var body: some View {
        HStack {
            landmark.image(forSize: 50)
            Text(landmark.name)
        }
    }
}

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 声明的外面来精简代码。

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

LandmarkRow.swift

import SwiftUI

struct LandmarkRow: View {
    var landmark: Landmark

    var body: some View {
        HStack {
            landmark.image(forSize: 50)
            Text(landmark.name)
        }
    }
}

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 在 canvas 中的显示。因为 #if DEBUG 指令的存在,当 app 发布时,编译器会删除这些代码。

4. 建立地标列表

使用 SwiftUIList 类型能够显示平台特有的列表 view 。列表的元素能够是静态的,就像咱们建立的 stacks 的子 view 同样;也能够是动态生成的。甚至能够把静态和动态生成的 view 混合在一块儿。

4.1 建立一个新的 SwiftUI view,命名为 LandmarkList.swift

4.2 把默认的 Text view 换成 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 指定单个元素,咱们还能够直接从集合中生成 row

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

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

list 使用 identifiable 的数据,咱们可使用如下两个方法之一来让数据变成 identifiable :调用 identified(by:) 方法,使用 key path 属性来惟一标识每一个元素,或者让数据类型遵循 Identifiable 协议。

LandmarkList.swift

import SwiftUI

struct LandmarkList: View {
    var body: some View {
        List(landmarkData.identified(by: \.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.identified(by: \.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)
    }

    func image(forSize size: Int) -> Image {
        ImageStore.shared.image(name: imageName, size: size)
    }

    enum Category: String, CaseIterable, Codable, Hashable {
        case featured = "Featured"
        case lakes = "Lakes"
        case rivers = "Rivers"
    }
}

struct Coordinates: Hashable, Codable {
    var latitude: Double
    var longitude: Double
}
复制代码

5.4 切回 LandmarkList,删除 identified(by:) 的调用。

从如今开始,咱们能够直接使用 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 中,并把每一个 row 嵌套在一个 NavigationButton 中来设置到目标 view 的转场,这样 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 的闭包中,把返回的 row 包装在一个 NavigationButton 中,并把 LandmarkDetail view 做为目标。

LandmarkList.swift

import SwiftUI

struct LandmarkList: View {
    var body: some View {
        NavigationView {
            List(landmarkData) { landmark in
                NavigationButton(destination: LandmarkDetail()) {
                    LandmarkRow(landmark: landmark)
                }
            }
            .navigationBarTitle(Text("Landmarks"))
        }
    }
}

struct LandmarkList_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkList()
    }
}
复制代码

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

7. 给子 View 传递数据

LandmarkDetail 如今依然使用硬编码的数据来显示地标。像 LandmarkRow 同样,LandmarkDetail 类型和它组合的其余 view 都须要一个 landmark 属性做为它们的数据源。

在开始子 view 的内容时,咱们会把 CircleImageMapViewLandmarkDetail 的显示从硬编码改成传入的数据。

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

这是使用 SwiftUI 构建 view 时的常见模式。咱们的自定义 view 一般会为特定视图包装和封装一些 modifiers

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(forSize: 250))
                .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:) 方法,给导航栏添加显示详情 view 时的标题。

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(forSize: 250))
                .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
        let window = UIWindow(frame: UIScreen.main.bounds)
        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
                NavigationButton(destination: LandmarkDetail(landmark: landmark)) {
                    LandmarkRow(landmark: landmark)
                }
            }
            .navigationBarTitle(Text("Landmarks"))
        }
    }
}

struct LandmarkList_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkList()
    }
}
复制代码

7.11 切换到实时预览,能够查看从列表导航到正确的地标详情 view 了。

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
                NavigationButton(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 相同的方式对集合进行操做,这样咱们就能够在任何可使用子视图的地方使用它,好比 stackslistsgroups 等。当数据元素像这里使用的字符串同样是简单的值类型时,咱们可使用 \.self 做为标识符的 key path

LandmarkList.swift

import SwiftUI

struct LandmarkList: View {
    var body: some View {
        NavigationView {
            List(landmarkData) { landmark in
                NavigationButton(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"].identified(by: \.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
                NavigationButton(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"].identified(by: \.self)) { deviceName in
            LandmarkList()
                .previewDevice(PreviewDevice(rawValue: deviceName))
                .previewDisplayName(deviceName)
        }
    }
}
复制代码

8.4 咱们能够在 canvas 中体验不一样的设备,对比它们在渲染 view 时的差别。

相关文章
相关标签/搜索