[譯] 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 時的差別。

相關文章
相關標籤/搜索