SwiftUI 02-構(gòu)建列表和導(dǎo)航(Building Lists and Navigation)

本章Demo 鏈接
Blog 鏈接

簡(jiǎn)介

此示例是記錄學(xué)習(xí)SwiftUI的過(guò)程,原文出自
SwiftUI Essentials Building Lists and Navigation
。

SwiftUI 01-創(chuàng)建和組合視圖 (Creating and Combining Views) 中創(chuàng)建了landmark的詳情頁(yè),本節(jié)我們做landmark的列表頁(yè)。

我們將創(chuàng)建可以顯示任何landmark信息的視圖,并動(dòng)態(tài)生成一個(gè)滾動(dòng)列表,用戶(hù)可以點(diǎn)擊該列表查看landmark的詳細(xì)視圖。要微調(diào)UI,我們將使用Xcode的畫(huà)布以不同的設(shè)備大小呈現(xiàn)多個(gè)預(yù)覽。

下載項(xiàng)目文件以開(kāi)始構(gòu)建此項(xiàng)目,并按照以下步驟操作。

第一節(jié) 了解示例中的數(shù)據(jù)

在第一個(gè)教程中, 我們直接把數(shù)據(jù)寫(xiě)死在視圖的代碼中?,F(xiàn)在我們將學(xué)習(xí)把數(shù)據(jù)傳遞給自定義視圖以供其顯示。
首先下載入門(mén)項(xiàng)目并熟悉示例數(shù)據(jù)。

2f8f9d15-348e-4c7f-b53d-be31a5d8c457.png
  • 1.在Project導(dǎo)航器中,選擇Models> Landmark.swift。
    Landmark.swift聲明了一個(gè)Landmark的結(jié)構(gòu)體,用于存儲(chǔ)應(yīng)用程序需要顯示的所有landmark信息,根據(jù)landmarkData.json中其中一組數(shù)據(jù)的所有字段構(gòu)建的landmark的model。

  • 2.在Project導(dǎo)航器中,選擇Resources> landmarkData.json。

我們將在本教程的其余部分以及隨后的所有內(nèi)容中使用此示例數(shù)據(jù)。

我們將在此以及以下每個(gè)教程中創(chuàng)建多個(gè)view的類(lèi)。

第2節(jié) 創(chuàng)建行視圖

我們將在本教程中構(gòu)建的第一個(gè)視圖是用于顯示每個(gè)地標(biāo)的詳細(xì)信息的行。 此行視圖將信息存儲(chǔ)在其顯示的地標(biāo)的屬性中,以便一個(gè)視圖可以顯示任何地標(biāo)。 稍后,您將多個(gè)行組合成一個(gè)地標(biāo)列表。

6f1c3da5-34c7-4c27-ba77-270ed2a29272.png
  • 1.創(chuàng)建一個(gè)名為LandmarkRow.swift的新SwiftUI視圖。

  • 2.如果預(yù)覽不可見(jiàn),請(qǐng)通過(guò)選擇Editor > Editor and Canvas來(lái)顯示畫(huà)布,然后單擊Get Started。

  • 3.添加LandmarkRow類(lèi)中添加一個(gè)Landmark類(lèi)型的landmark屬性作為這個(gè)視圖的模型。

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屬性時(shí),預(yù)覽將停止工作,因?yàn)?code>LandmarkRow類(lèi)型在初始化期間需要傳入一個(gè)Landmark的模型。

Snip20190609_41.png

要修復(fù)預(yù)覽,我們需要修改預(yù)覽中初始化LandmarkRow()的代碼。

  • 4.在LandmarkRow_Previews的預(yù)覽靜態(tài)屬性中,將landmark參數(shù)添加到LandmarkRow()初始化方法的參數(shù)中,指定landmarkData數(shù)組的第一個(gè)元素。

預(yù)覽顯示文本Hello World。

修復(fù)后,您可以為行構(gòu)建布局。

  • 5.將現(xiàn)有文本視圖嵌入HStack中。

  • 6.修改文本視圖以使用landmark屬性的name。

  • 7.通過(guò)在文本視圖之前添加圖像來(lái)完成行。

Snip20190609_42.png

第3節(jié) 自定義行預(yù)覽

Xcode的畫(huà)布自動(dòng)識(shí)別并顯示當(dāng)前編輯器中符合PreviewProvider協(xié)議的任何類(lèi)型。 預(yù)覽提供程序返回一個(gè)或多個(gè)視圖,其中包含用于配置大小和設(shè)備的選項(xiàng)。

我們可以從預(yù)覽提供程序自定義返回的內(nèi)容,以準(zhǔn)確呈現(xiàn)對(duì)我們最有幫助的預(yù)覽。

de4301fa-bf9c-45ac-b7f2-bbb990ccb41b.png
  • 1.在LandmarkRow_Previews中,將landmark參數(shù)更新為landmarkData數(shù)組中的第二個(gè)元素。

修改后預(yù)覽立即更改以顯示第二個(gè)model的數(shù)據(jù)而不是第一個(gè)。

  • 2.使用previewLayout(_ :)方法設(shè)置列表中這一行的cell大小。

我們可以在previews中使用group返回多行cell預(yù)覽。

  • 3.將返回的行換放到一個(gè)Group中,然后在Group中再添加一個(gè)LandmarkRow。
Snip20190609_43.png
Snip20190609_44.png

Group是用于對(duì)視圖內(nèi)容進(jìn)行分組的容器。 Xcode將組的子視圖渲染為畫(huà)布中的單獨(dú)預(yù)覽。

  • 4.要簡(jiǎn)化代碼,請(qǐng)將previewLayout(_ :)調(diào)用移動(dòng)到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

視圖的子項(xiàng)繼承視圖的上下文設(shè)置,例如預(yù)覽配置。

我們?cè)陬A(yù)覽提供程序中編寫(xiě)的代碼僅更改Xcode在畫(huà)布中顯示的內(nèi)容。 由于#if DEBUG指令,編譯器會(huì)刪除代碼,因此它在release下不會(huì)打包到應(yīng)用程序中。

第4節(jié) 創(chuàng)建landmark列表

使用SwiftUIList類(lèi)型時(shí),可以顯示特定于平臺(tái)的視圖列表。 列表的元素可以是靜態(tài)的,就像我們目前創(chuàng)建的堆棧的子視圖一樣,或者是動(dòng)態(tài)生成的。 您甚至可以混合靜態(tài)和動(dòng)態(tài)生成的視圖。

c91b6546-1230-43a2-8867-2f0e445edb99.png
  • 1.創(chuàng)建一個(gè)名為LandmarkList.swift的新SwiftUI視圖。

  • 2.用List替換模板中默認(rèn)的Text視圖,并在List中添加兩個(gè)LandmarkRow初始化的視圖作為兩行顯示。

現(xiàn)在我們預(yù)覽顯示以適合iOS的列表樣式呈現(xiàn)的兩個(gè)landmark,這個(gè)List不就是UITableView嗎?

Snip20190609_45.png

第5節(jié) 使列表動(dòng)態(tài)化

我們可以直接從集合中生成行,而不是單獨(dú)指定列表的元素。

我們可以通過(guò)傳遞數(shù)據(jù)集合以及為集合中的每個(gè)元素提供視圖的閉包來(lái)創(chuàng)建顯示集合元素的列表。 該列表使用提供的閉包將集合中的每個(gè)元素轉(zhuǎn)換為子視圖。

3f2f8071-97e1-481e-92a2-efb18be01ec7-1.png
  • 1.刪除List中兩個(gè)靜態(tài)的LandmarkRow,然后將landmarkData傳遞給List的初始化方法中。

列表使用可識(shí)別的數(shù)據(jù)。 我們可以通過(guò)以下兩種方式之一來(lái)識(shí)別數(shù)據(jù):通過(guò)調(diào)用identified(by:)方法,使用唯一標(biāo)識(shí)每個(gè)元素的屬性的鍵路徑,或者使您的數(shù)據(jù)類(lèi)型遵守Identifiable協(xié)議。

  • 2.通過(guò)從閉包返回LandmarkRow來(lái)完成動(dòng)態(tài)生成的列表。

這為landmarkData數(shù)組中的每個(gè)元素創(chuàng)建一個(gè)LandmarkRow

接下來(lái),我們將通過(guò)向Landmark類(lèi)型添加Identifiable協(xié)議來(lái)簡(jiǎn)化List代碼。

  • 3.切換到Landmark.swift并聲明符合可識(shí)別協(xié)議。

由于Landmark類(lèi)型已經(jīng)具有Identifiable協(xié)議所需的id屬性,因此沒(méi)有其他工作要做。

  • 4.切換回LandmarkList.swift并刪除landmarkData調(diào)用的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)在開(kāi)始,我們將能夠直接使用Landmark元素的集合。

第6節(jié) 在列表和詳細(xì)信息之間設(shè)置導(dǎo)航

列表正確呈現(xiàn),但我們無(wú)法點(diǎn)擊某個(gè)landmark以查看它的詳細(xì)信息頁(yè)面。

通過(guò)將導(dǎo)航功能嵌入到NavigationView中,然后將每行嵌套在NavigationButton中以設(shè)置到目標(biāo)視圖的轉(zhuǎn)換,可以將導(dǎo)航功能添加到列表中。

c6f21df9-1d6d-42d4-b21b-0a2588f4cd97.png
  • 1.在NavigationView中嵌入動(dòng)態(tài)生成的landmark列表。

  • 2.調(diào)用navigationBarTitle(_ :)修飾符方法以在顯示列表時(shí)設(shè)置導(dǎo)航欄的標(biāo)題。

Snip20190609_48.png
  • 3.在列表的閉包內(nèi),將返回的行包裝在NavigationButton中,將LandmarkDetail視圖指定為目標(biāo)。

  • 4.我們可以通過(guò)切換到實(shí)時(shí)模式直接在預(yù)覽中嘗試導(dǎo)航。 單擊“實(shí)時(shí)預(yù)覽”按鈕,然后點(diǎn)擊地標(biāo)以訪問(wèn)詳細(xì)信息頁(yè)面。

Snip20190609_49.png

第7節(jié) 將數(shù)據(jù)傳遞到子視圖

LandmarkDetail視圖中是使用寫(xiě)死的數(shù)據(jù)展示UI 的。 就像LandmarkRow一樣,我們需要在LandmarkDetail中添加一個(gè)Landmark類(lèi)型的模型以作為其數(shù)據(jù)源顯示view。

從子視圖開(kāi)始,我們將轉(zhuǎn)換CircleImageMapViewLandmarkDetail以顯示傳入的數(shù)據(jù),而不是將數(shù)據(jù)寫(xiě)死在代碼中。

cb49732f-cb3e-4e77-a2e6-84f7f7618502.png
  • 1.在CircleImage.swift中,將存儲(chǔ)的圖像屬性添加到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構(gòu)建視圖時(shí)的常見(jiàn)模式。 自定義視圖通常會(huì)包裝并封裝特定視圖的一系列修飾符。

  • 2.更新CircleImage_Previews,給其傳遞一個(gè)Turtle Rock的圖像使其可以正常預(yù)覽。
  • 3.在MapView.swift中,向MapView添加一個(gè)坐標(biāo)屬性,并轉(zhuǎn)換代碼以使用該屬性,而不是在代碼中寫(xiě)死緯度和經(jīng)度數(shù)據(jù)。

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.更新預(yù)覽提供程序以傳遞數(shù)據(jù)數(shù)組中第一個(gè)landmark的坐標(biāo)。

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類(lèi)型的屬性添加到LandmarkDetail類(lèi)型中,作為其model數(shù)據(jù)。

  • 6.更新預(yù)覽以使用landmarkData中的第一個(gè)lanmark

  • 7.完成將所需數(shù)據(jù)傳遞給您的自定義類(lèi)型。

  • 8.最后,調(diào)用navigationBarTitle(_:displayMode :)方法,在顯示詳細(xì)視圖時(shí)為導(dǎo)航欄指定標(biāo)題。

  • 9.在SceneDelegate.swift中,將rootViewControllerrootView修改為為LandmarkList,這樣首頁(yè)展示的就是列表頁(yè)了。

當(dāng)我們?cè)谀M器中獨(dú)立運(yùn)行而不是預(yù)覽時(shí),我們的應(yīng)用程序?qū)⒁?code>SceneDelegate中定義的根視圖開(kāi)始。

  • 10.在LandmarkList.swift中,將當(dāng)前landmark傳遞到目標(biāo)LandmarkDetail中,關(guān)鍵代碼為NavigationButton(destination: LandmarkDetail(landmark: landmark))。
import SwiftUI

struct LandmarkList : View {
    var body: some View {
        // 設(shè)置導(dǎo)航容器
        NavigationView {
            // 初始化一個(gè)類(lèi)型TableView的view
            List(landmarkData) { landmark in
                // 點(diǎn)擊cell時(shí),將當(dāng)前`landmark`傳遞到目標(biāo)`LandmarkDetail`中。
                NavigationButton(destination: LandmarkDetail(landmark: landmark)) {
                    
                    LandmarkRow(landmark: landmark)
                }
            }
            // 顯示當(dāng)前列表頁(yè)的導(dǎo)航標(biāo)題
            .navigationBarTitle(Text("Landmarks"))
        }
        
    }
}

#if DEBUG
struct LandmarkList_Previews : PreviewProvider {
    static var previews: some View {
        LandmarkList()
    }
}
#endif

  • 11.切換到實(shí)時(shí)預(yù)覽以查看從列表導(dǎo)航時(shí)詳細(xì)視圖顯示正確的標(biāo)志

第8節(jié) 動(dòng)態(tài)生成預(yù)覽

接下來(lái),我們將向LandmarkList_Previews預(yù)覽提供程序添加代碼,以顯示不同設(shè)備大小的列表視圖的預(yù)覽。 默認(rèn)情況下,預(yù)覽會(huì)以活動(dòng)方案中設(shè)備的大小進(jìn)行渲染。 我們可以通過(guò)調(diào)用previewDevice(_ :)方法來(lái)更改預(yù)覽設(shè)備。

475e4ac3-b605-4309-a294-e9d2efa6f1ab.png
  • 1.首先,將當(dāng)前列表預(yù)覽更改為以iPhone SE的大小呈現(xiàn)。
#if DEBUG
struct LandmarkList_Previews : PreviewProvider {
    static var previews: some View {
        LandmarkList()
            // 以iPhone SE 設(shè)備的大小預(yù)覽畫(huà)布
            .previewDevice(PreviewDevice(rawValue: "iPhone SE"))
        
    }
}
#endif

我們可以提供Xcode方案菜單中顯示的任何設(shè)備的名稱(chēng)。

Snip20190609_50.png
  • 2.在畫(huà)布中添加對(duì)多個(gè)設(shè)備的預(yù)覽
    在預(yù)覽的列表中,使用設(shè)備名稱(chēng)數(shù)組作為數(shù)據(jù),將LandmarkList嵌入到ForEach實(shí)例中。
Snip20190609_51.png

ForEach以與列表相同的方式對(duì)集合進(jìn)行操作,這意味著我們可以在任何可以使用子視圖的位置使用它,例如在堆棧,列表,組等中。 當(dāng)數(shù)據(jù)元素是簡(jiǎn)單的值類(lèi)型 - 就像在這里使用的字符串一樣 - 我們可以使用\ .self作為標(biāo)識(shí)符的關(guān)鍵路徑。

  • 3.使用previewDisplayName(_ :)修飾符將設(shè)備名稱(chēng)添加為預(yù)覽的標(biāo)簽。
Snip20190609_52.png
  • 4.我們可以嘗試使用不同的設(shè)備來(lái)比較視圖的渲染,所有這些都來(lái)自畫(huà)布。

測(cè)試

  • 1.除了List之外,這些類(lèi)型中的哪一個(gè)提供了集合中的動(dòng)態(tài)視圖列表?

可選項(xiàng):
1.Group
2.ForEath
3.UITableView

答案:2

  • 2.我們可以從可識(shí)別元素集合中創(chuàng)建視圖列表。 我們使用什么方法來(lái)調(diào)整不符合可識(shí)別協(xié)議的元素集合?

可選項(xiàng):
1.func map(_:)
2.func sorted(by:)
3.func identified(by:)

答案:3

  • 3.用哪種類(lèi)型來(lái)使List的行可以導(dǎo)航到另一個(gè)視圖?

可選項(xiàng):
1.NavigationButton
2.UITableViewDelegate
3.NavigationView

答案: 1

  • 4.哪些選項(xiàng)不是設(shè)置設(shè)備以預(yù)覽視圖的方法?

可選項(xiàng):

  1. Change the simulator selected in the active scheme.
  2. Make a different choice in Canvas Settings in Xcode’s preferences.
  3. Specify one or more devices using the previewDevice(_:)method.
  4. Connect your development device and click the Device Preview button.

答案:2

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 由于 API 變動(dòng),此文章部分內(nèi)容已失效,最新完整中文教程及代碼請(qǐng)查看 https://github.com/Wi...
    Willie_閱讀 6,566評(píng)論 12 14
  • 版本記錄 前言 SwiftUI是2019年WWDC新推出的UI相關(guān)的API,相信大家都已經(jīng)知道并想體驗(yàn)一下,下面我...
    刀客傳奇閱讀 23,094評(píng)論 0 16
  • 1. 孩子合適磨磨嘰嘰了?為何磨磨嘰嘰? 快速結(jié)果意味著另一項(xiàng)任務(wù)?逃避新任務(wù)? 磨磨嘰嘰的過(guò)程中,雖然跟媽媽口舌...
    荷方閱讀 333評(píng)論 0 1
  • 冬季,太陽(yáng)直射到地面的時(shí)候,空氣中氤氳著帶些暖意的清香,老廿和我會(huì)出門(mén)散步,順著泛白的河流和堆積著零星枯葉的街道,...
    蘭菲兒閱讀 871評(píng)論 0 3

友情鏈接更多精彩內(nèi)容