SwiftUI 02-構建列表和導航(Building Lists and Navigation)

本章Demo 鏈接
Blog 鏈接

簡介

此示例是記錄學習SwiftUI的過程,原文出自
SwiftUI Essentials Building Lists and Navigation

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

我們將創(chuàng)建可以顯示任何landmark信息的視圖,并動態(tài)生成一個滾動列表,用戶可以點擊該列表查看landmark的詳細視圖只恨。要微調UI贴膘,我們將使用Xcode的畫布以不同的設備大小呈現(xiàn)多個預覽卖子。

下載項目文件以開始構建此項目,并按照以下步驟操作刑峡。

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

在第一個教程中洋闽, 我們直接把數據寫死在視圖的代碼中。現(xiàn)在我們將學習把數據傳遞給自定義視圖以供其顯示突梦。
首先下載入門項目并熟悉示例數據诫舅。

2f8f9d15-348e-4c7f-b53d-be31a5d8c457.png
  • 1.在Project導航器中,選擇Models> Landmark.swift宫患。
    Landmark.swift聲明了一個Landmark的結構體刊懈,用于存儲應用程序需要顯示的所有landmark信息,根據landmarkData.json中其中一組數據的所有字段構建的landmark的model娃闲。

  • 2.在Project導航器中虚汛,選擇Resources> landmarkData.json

我們將在本教程的其余部分以及隨后的所有內容中使用此示例數據皇帮。

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

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

我們將在本教程中構建的第一個視圖是用于顯示每個地標的詳細信息的行将谊。 此行視圖將信息存儲在其顯示的地標的屬性中冷溶,以便一個視圖可以顯示任何地標。 稍后瓢娜,您將多個行組合成一個地標列表挂洛。

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

  • 2.如果預覽不可見眠砾,請通過選擇Editor > Editor and Canvas來顯示畫布虏劲,然后單擊Get Started

  • 3.添加LandmarkRow類中添加一個Landmark類型的landmark屬性作為這個視圖的模型褒颈。

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屬性時柒巫,預覽將停止工作,因為LandmarkRow類型在初始化期間需要傳入一個Landmark的模型谷丸。

Snip20190609_41.png

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

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

預覽顯示文本Hello World揩慕。

修復后亭畜,您可以為行構建布局。

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

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

  • 7.通過在文本視圖之前添加圖像來完成行。

Snip20190609_42.png

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

Xcode的畫布自動識別并顯示當前編輯器中符合PreviewProvider協(xié)議的任何類型蜗搔。 預覽提供程序返回一個或多個視圖劲藐,其中包含用于配置大小和設備的選項。

我們可以從預覽提供程序自定義返回的內容樟凄,以準確呈現(xiàn)對我們最有幫助的預覽聘芜。

de4301fa-bf9c-45ac-b7f2-bbb990ccb41b.png
  • 1.在LandmarkRow_Previews中,將landmark參數更新為landmarkData數組中的第二個元素缝龄。

修改后預覽立即更改以顯示第二個model的數據而不是第一個汰现。

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

我們可以在previews中使用group返回多行cell預覽二拐。

  • 3.將返回的行換放到一個Group中服鹅,然后在Group中再添加一個LandmarkRow
Snip20190609_43.png
Snip20190609_44.png

Group是用于對視圖內容進行分組的容器百新。 Xcode將組的子視圖渲染為畫布中的單獨預覽企软。

  • 4.要簡化代碼,請將previewLayout(_ :)調用移動到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

視圖的子項繼承視圖的上下文設置仗哨,例如預覽配置形庭。

我們在預覽提供程序中編寫的代碼僅更改Xcode在畫布中顯示的內容。 由于#if DEBUG指令厌漂,編譯器會刪除代碼萨醒,因此它在release下不會打包到應用程序中。

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

使用SwiftUIList類型時苇倡,可以顯示特定于平臺的視圖列表富纸。 列表的元素可以是靜態(tài)的,就像我們目前創(chuàng)建的堆棧的子視圖一樣旨椒,或者是動態(tài)生成的晓褪。 您甚至可以混合靜態(tài)和動態(tài)生成的視圖。

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

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

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

Snip20190609_45.png

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

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

我們可以通過傳遞數據集合以及為集合中的每個元素提供視圖的閉包來創(chuàng)建顯示集合元素的列表钧汹。 該列表使用提供的閉包將集合中的每個元素轉換為子視圖。

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

列表使用可識別的數據类嗤。 我們可以通過以下兩種方式之一來識別數據:通過調用identified(by:)方法糊肠,使用唯一標識每個元素的屬性的鍵路徑,或者使您的數據類型遵守Identifiable協(xié)議遗锣。

  • 2.通過從閉包返回LandmarkRow來完成動態(tài)生成的列表货裹。

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

接下來精偿,我們將通過向Landmark類型添加Identifiable協(xié)議來簡化List代碼弧圆。

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

由于Landmark類型已經具有Identifiable協(xié)議所需的id屬性笔咽,因此沒有其他工作要做搔预。

  • 4.切換回LandmarkList.swift并刪除landmarkData調用的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)在開始叶组,我們將能夠直接使用Landmark元素的集合拯田。

第6節(jié) 在列表和詳細信息之間設置導航

列表正確呈現(xiàn),但我們無法點擊某個landmark以查看它的詳細信息頁面甩十。

通過將導航功能嵌入到NavigationView中船庇,然后將每行嵌套在NavigationButton中以設置到目標視圖的轉換吭产,可以將導航功能添加到列表中。

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

  • 2.調用navigationBarTitle(_ :)修飾符方法以在顯示列表時設置導航欄的標題臣淤。

Snip20190609_48.png
  • 3.在列表的閉包內,將返回的行包裝在NavigationButton中窃爷,將LandmarkDetail視圖指定為目標邑蒋。

  • 4.我們可以通過切換到實時模式直接在預覽中嘗試導航。 單擊“實時預覽”按鈕按厘,然后點擊地標以訪問詳細信息頁面寺董。

Snip20190609_49.png

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

LandmarkDetail視圖中是使用寫死的數據展示UI 的。 就像LandmarkRow一樣刻剥,我們需要在LandmarkDetail中添加一個Landmark類型的模型以作為其數據源顯示view遮咖。

從子視圖開始,我們將轉換CircleImage造虏,MapViewLandmarkDetail以顯示傳入的數據御吞,而不是將數據寫死在代碼中。

cb49732f-cb3e-4e77-a2e6-84f7f7618502.png
  • 1.在CircleImage.swift中漓藕,將存儲的圖像屬性添加到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構建視圖時的常見模式。 自定義視圖通常會包裝并封裝特定視圖的一系列修飾符享钞。

  • 2.更新CircleImage_Previews揍诽,給其傳遞一個Turtle Rock的圖像使其可以正常預覽。
  • 3.在MapView.swift中栗竖,向MapView添加一個坐標屬性暑脆,并轉換代碼以使用該屬性,而不是在代碼中寫死緯度和經度數據狐肢。

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.更新預覽提供程序以傳遞數據數組中第一個landmark的坐標添吗。

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類型的屬性添加到LandmarkDetail類型中份名,作為其model數據碟联。

  • 6.更新預覽以使用landmarkData中的第一個lanmark

  • 7.完成將所需數據傳遞給您的自定義類型僵腺。

  • 8.最后鲤孵,調用navigationBarTitle(_:displayMode :)方法,在顯示詳細視圖時為導航欄指定標題辰如。

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

當我們在模擬器中獨立運行而不是預覽時鹰椒,我們的應用程序將以SceneDelegate中定義的根視圖開始锡移。

  • 10.在LandmarkList.swift中,將當前landmark傳遞到目標LandmarkDetail中漆际,關鍵代碼為NavigationButton(destination: LandmarkDetail(landmark: landmark))淆珊。
import SwiftUI

struct LandmarkList : View {
    var body: some View {
        // 設置導航容器
        NavigationView {
            // 初始化一個類型TableView的view
            List(landmarkData) { landmark in
                // 點擊cell時,將當前`landmark`傳遞到目標`LandmarkDetail`中奸汇。
                NavigationButton(destination: LandmarkDetail(landmark: landmark)) {
                    
                    LandmarkRow(landmark: landmark)
                }
            }
            // 顯示當前列表頁的導航標題
            .navigationBarTitle(Text("Landmarks"))
        }
        
    }
}

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

  • 11.切換到實時預覽以查看從列表導航時詳細視圖顯示正確的標志

第8節(jié) 動態(tài)生成預覽

接下來施符,我們將向LandmarkList_Previews預覽提供程序添加代碼,以顯示不同設備大小的列表視圖的預覽擂找。 默認情況下戳吝,預覽會以活動方案中設備的大小進行渲染。 我們可以通過調用previewDevice(_ :)方法來更改預覽設備贯涎。

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

我們可以提供Xcode方案菜單中顯示的任何設備的名稱塘雳。

Snip20190609_50.png
  • 2.在畫布中添加對多個設備的預覽
    在預覽的列表中陆盘,使用設備名稱數組作為數據,將LandmarkList嵌入到ForEach實例中败明。
Snip20190609_51.png

ForEach以與列表相同的方式對集合進行操作隘马,這意味著我們可以在任何可以使用子視圖的位置使用它,例如在堆棧妻顶,列表酸员,組等中。 當數據元素是簡單的值類型 - 就像在這里使用的字符串一樣 - 我們可以使用\ .self作為標識符的關鍵路徑讳嘱。

  • 3.使用previewDisplayName(_ :)修飾符將設備名稱添加為預覽的標簽幔嗦。
Snip20190609_52.png
  • 4.我們可以嘗試使用不同的設備來比較視圖的渲染,所有這些都來自畫布呢燥。

測試

  • 1.除了List之外崭添,這些類型中的哪一個提供了集合中的動態(tài)視圖列表寓娩?

可選項:
1.Group
2.ForEath
3.UITableView

答案:2

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

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

答案:3

  • 3.用哪種類型來使List的行可以導航到另一個視圖棘伴?

可選項:
1.NavigationButton
2.UITableViewDelegate
3.NavigationView

答案: 1

  • 4.哪些選項不是設置設備以預覽視圖的方法寞埠?

可選項:

  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

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市焊夸,隨后出現(xiàn)的幾起案子仁连,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饭冬,死亡現(xiàn)場離奇詭異使鹅,居然都是意外死亡,警方通過查閱死者的電腦和手機昌抠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門患朱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人炊苫,你說我怎么就攤上這事裁厅。” “怎么了侨艾?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵执虹,是天一觀的道長。 經常有香客問我唠梨,道長袋励,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任当叭,我火速辦了婚禮插龄,結果婚禮上,老公的妹妹穿的比我還像新娘科展。我一直安慰自己均牢,他們只是感情好,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布才睹。 她就那樣靜靜地躺著徘跪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪琅攘。 梳的紋絲不亂的頭發(fā)上垮庐,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機與錄音坞琴,去河邊找鬼哨查。 笑死,一個胖子當著我的面吹牛剧辐,可吹牛的內容都是我干的寒亥。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼荧关,長吁一口氣:“原來是場噩夢啊……” “哼溉奕!你這毒婦竟也來了?” 一聲冷哼從身側響起忍啤,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤加勤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體鳄梅,經...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡叠国,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了戴尸。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片煎饼。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖校赤,靈堂內的尸體忽然破棺而出吆玖,到底是詐尸還是另有隱情,我是刑警寧澤马篮,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布沾乘,位于F島的核電站,受9級特大地震影響浑测,放射性物質發(fā)生泄漏翅阵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一迁央、第九天 我趴在偏房一處隱蔽的房頂上張望掷匠。 院中可真熱鬧,春花似錦岖圈、人聲如沸讹语。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽顽决。三九已至,卻和暖如春导匣,著一層夾襖步出監(jiān)牢的瞬間才菠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工贡定, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留赋访,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓缓待,卻偏偏與公主長得像蚓耽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子命斧,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內容

  • 由于 API 變動田晚,此文章部分內容已失效,最新完整中文教程及代碼請查看 https://github.com/Wi...
    Willie_閱讀 6,485評論 12 14
  • 版本記錄 前言 SwiftUI是2019年WWDC新推出的UI相關的API国葬,相信大家都已經知道并想體驗一下,下面我...
    刀客傳奇閱讀 22,732評論 0 16
  • 1. 孩子合適磨磨嘰嘰了?為何磨磨嘰嘰汇四? 快速結果意味著另一項任務接奈?逃避新任務? 磨磨嘰嘰的過程中通孽,雖然跟媽媽口舌...
    荷方閱讀 257評論 0 1
  • 冬季序宦,太陽直射到地面的時候,空氣中氤氳著帶些暖意的清香背苦,老廿和我會出門散步互捌,順著泛白的河流和堆積著零星枯葉的街道,...
    蘭菲兒閱讀 819評論 0 3