SwiftUI學(xué)習(xí)100天(Day82 - 項目 16,第四部分)

原創(chuàng)鏈接:https://www.hackingwithswift.com/100/swiftui
以下內(nèi)容僅供學(xué)習(xí)參考:

是時候開始將你的新技術(shù)付諸實(shí)踐了,這個項目太大了,需要三天的實(shí)施時間才能完成。但今天是第 82 天,所以你已經(jīng)證明了你有創(chuàng)造奇跡的意志力——正如航空先驅(qū) Amelia Earhart 曾經(jīng)說過的,“最困難的事情是行動的決定,剩下的只是堅韌?!?/p>
今天介紹了很多有趣的技術(shù),但我也會簡要地向你介紹filter()
序列方法。我們之前看過map()
,它用于將序列中的對象從一個事物轉(zhuǎn)換為另一個事物,filter 的工作方式類似:它是一個序列方法,它接受一個閉包,該閉包分別在每個元素上運(yùn)行,并返回一個新陣列。
不同之處在于我們傳遞的閉包filter()
用作謂詞——用于確定每個元素是否應(yīng)包含在返回數(shù)組中的測試。如果測試為某個元素返回 true,則它會被包含,否則將被跳過。
map()
和
范疇。這在我的書Pro Swift中有詳細(xì)介紹,但簡略的定義是我們的代碼告訴計算機(jī)做什么而不是如何做。在filter()
都屬于稱為函數(shù)式編程的map()
中我們說“遍歷這個數(shù)組中的每個項目,使用這個閉包轉(zhuǎn)換它,并將結(jié)果放回一個新數(shù)組”
但是要由 Swift 來弄清楚如何實(shí)現(xiàn)這一點(diǎn)。filter()的情況下,我們在做很多相同的事情:“遍歷這個數(shù)組中的每一項,對每一項運(yùn)行這個測試,并將通過測試的任何項放入一個新數(shù)組中?!?/code>
不管怎樣,聊夠了——你今天有很多事情要完成,所以讓我們開始寫代碼吧!
今天你要完成三個主題,你將在其中了解選項卡視圖、環(huán)境對象filter()
等。

構(gòu)建我們的標(biāo)簽欄
這個應(yīng)用程序?qū)⒃跇?biāo)簽欄中顯示四個 SwiftUI 視圖:一個顯示你遇到的每個人,一個顯示你聯(lián)系過的人,另一個顯示你沒有聯(lián)系過的人,最后一個顯示你的個人信息給其他人掃描。
前三個視圖是同一概念的變體,但最后一個則完全不同。因此,我們可以只用三個視圖來表示我們所有的 UI:一個顯示人員,一個顯示我們的數(shù)據(jù),一個使用TabView
.
因此,我們的第一步是為我們的選項卡創(chuàng)建占位符視圖,我們可以稍后返回并填寫。按 Cmd+N 創(chuàng)建一個新的 SwiftUI 視圖并將其命名為“ProspectsView”,然后創(chuàng)建另一個名為“MeView”的 SwiftUI 視圖。你可以將它們都保留為默認(rèn)的“Hello, World!”?文本視圖;現(xiàn)在沒關(guān)系。
現(xiàn)在,重要的是ContentView
,因?yàn)檫@是我們要存儲我們TabView
的 UI 中包含所有其他視圖的地方。我們很快會在這里添加更多邏輯,但現(xiàn)在這只是一個TabView
具有三個實(shí)例ProspectsView
和一個MeView
.?這些視圖中的每一個都會有一個tabItem()
修飾符,其中包含我從 SF Symbols 中挑選的圖像和一些文本。
用這個替換你當(dāng)前的主體ContentView
:
如果你現(xiàn)在運(yùn)行該應(yīng)用程序,你會在屏幕底部看到一個整潔的標(biāo)簽欄,允許我們點(diǎn)擊我們的四個視圖中的每一個。
現(xiàn)在,顯然創(chuàng)建三個ProspectsView
的實(shí)例
在實(shí)踐中會很奇怪,因?yàn)樗鼈兺耆嗤?,但我們可以通過自定義每個視圖來解決這個問題。請記住,我們希望第一個顯示你遇到的每個人,第二個顯示你聯(lián)系過的人,第三個顯示你沒有聯(lián)系過的人,我們可以用一個枚舉加上一個屬性來表示ProspectsView
。
所以,現(xiàn)在在ProspectsView
里面添加這個枚舉
:
現(xiàn)在我們可以通過給它一個新屬性來使用它來允許每個ProspectsView
實(shí)例
略有不同:
這將立即中斷ContentView
,ProspectsView_Previews
因?yàn)樗麄冃枰趧?chuàng)建時為該屬性提供一個值ProspectsView
,但首先讓我們通過給它們一個導(dǎo)航欄標(biāo)題來使用它來自定義三個視圖中的每一個。
首先將此計算屬性添加到ProspectsView
:
現(xiàn)在替換默認(rèn)的“Hello, World!”?正文:
這至少讓每個ProspectsView
實(shí)例看起來都略有不同,因此我們可以確保選項卡正常工作。
為了使我們的代碼再次編譯,我們需要確保每個ProspectsView
初始化程序都使用過濾器調(diào)用。因此,將ProspectsView_Previews
正文更改為:
然后更改三個ProspectsView
實(shí)例,ContentView
使它們分別具有filter: .none
、filter: .contacted
和filter: .uncontacted
。
如果你現(xiàn)在運(yùn)行該應(yīng)用程序,你會發(fā)現(xiàn)它看起來好多了?,F(xiàn)在面臨真正的挑戰(zhàn):前三個視圖需要處理相同的數(shù)據(jù),那么我們?nèi)绾尾拍茼樌蚕硭鼈兡??為此,我們需要求助?SwiftUI 的環(huán)境……



使用@EnvironmentObject 跨選項卡共享數(shù)據(jù)
SwiftUI 的環(huán)境讓我們以一種非常漂亮的方式共享數(shù)據(jù):任何視圖都可以將對象發(fā)送到環(huán)境中,然后任何子視圖都可以在以后從環(huán)境中讀回這些對象。更好的是,如果一個視圖更改了對象,所有其他視圖都會自動更新——這是在大型應(yīng)用程序中共享數(shù)據(jù)的一種非常聰明的方式。
在我們的應(yīng)用程序中,我們有一個TabView
包含三個實(shí)例的ProspectsView
,我們希望所有這三個實(shí)例都作為同一共享數(shù)據(jù)的不同視圖工作。這是 SwiftUI 環(huán)境有意義的一個很好的例子:我們可以定義一個存儲一個潛在客戶的類,然后將這些潛在客戶的數(shù)組放入環(huán)境中,這樣我們所有的視圖都可以在需要時讀取它。
因此,首先創(chuàng)建一個名為 Prospect.swift 的新 Swift 文件,將其 Foundation 導(dǎo)入替換為 SwiftUI,然后為其提供以下代碼:
是的,那是一個類而不是結(jié)構(gòu)。這是有意為之的,因?yàn)樗试S我們直接更改類的實(shí)例,并同時在所有其他視圖中更新它。請記住,SwiftUI 負(fù)責(zé)自動將更改傳播到我們的視圖,因此不存在視圖過時的風(fēng)險。
當(dāng)談到在多個視圖之間共享時,SwiftUI 環(huán)境的最好的事情之一是它使用ObservableObject
我們一直使用的與@StateObject
屬性包裝器相同的協(xié)議。這意味著我們可以標(biāo)記應(yīng)該使用@Published
屬性包裝器聲明的屬性——SwiftUI 會為我們處理大部分工作。
所以,在 Prospect.swift 中添加這個類:
稍后我們會回過頭來,尤其是讓初始化器做的不僅僅是創(chuàng)建一個空數(shù)組,但現(xiàn)在已經(jīng)足夠了。
現(xiàn)在,我們希望所有ProspectsView
實(shí)例共享該類的一個Prospects
實(shí)例
,因此它們都指向相同的數(shù)據(jù)。如果我們在這里編寫 UIKit 代碼,我會詳細(xì)解釋要做到這一點(diǎn)有多困難,以及我們需要多小心以確保所有更改都能干凈地傳播,但對于 SwiftUI,它只需要三個步驟。
首先,我們需要添加一個屬性給ContentView
來
創(chuàng)建和存儲類的單個實(shí)例Prospects
:
其次,我們需要將該屬性發(fā)布到 SwiftUI 環(huán)境中,以便所有子視圖都可以訪問它。因?yàn)檫x項卡被認(rèn)為是它們所在的選項卡視圖的子項,所以如果我們將它添加到TabView
環(huán)境中,那么
我們所有的ProspectsView
實(shí)例都將獲得該對象。
因此,將此修飾符添加到ContentView
的
TabView
:
重要提示:確保將相同的修飾符添加到ProspectsView
的預(yù)覽結(jié)構(gòu)中
,以便你的預(yù)覽繼續(xù)工作。
現(xiàn)在我們希望 ProspectsView
的所有實(shí)例
在創(chuàng)建時從環(huán)境中讀回該對象。這使用了一個新的@EnvironmentObject
屬性包裝器來完成查找對象、將其附加到屬性以及隨著時間的推移使其保持最新的所有工作。所以,最后一步只是將此屬性添加到ProspectsView
:
這真的就是它所需要的——我認(rèn)為 SwiftUI 沒有辦法讓這一切變得更容易。
重要提示:當(dāng)你使用@EnvironmentObject
時,
你是在明確告訴 SwiftUI,你的對象將在創(chuàng)建視圖時存在于環(huán)境中。如果它不存在,你的應(yīng)用程序?qū)⒘⒓幢罎?- 請小心,并將其視為隱式解包的可選項。
很快我們將添加代碼以通過掃描 QR 碼添加潛在客戶,但現(xiàn)在我們將添加一個導(dǎo)航欄項目,該項目僅添加測試數(shù)據(jù)并將其顯示在屏幕上。
將ProspectsView
為此:的body
屬性更改
現(xiàn)在你會在選項卡視圖的前三個視圖上看到一個“掃描”按鈕,點(diǎn)擊它會同時向所有三個視圖添加一個人——無論你點(diǎn)擊哪個按鈕,你都會看到計數(shù)增加。



動態(tài)過濾 SwiftUI 列表
SwiftUI 的List
視圖喜歡使用符合Identifiable
協(xié)議的對象數(shù)組,或者至少可以提供某種id
保證唯一的參數(shù)。然而,沒有理由需要將這些存儲在視圖的屬性中,事實(shí)上,如果我們發(fā)送一個計算屬性,那么我們就可以根據(jù)需要調(diào)整我們的過濾。
在我們的應(yīng)用程序中,我們有三個實(shí)例,ProspectsView
僅根據(jù)FilterType
從選項卡視圖傳入的屬性而有所不同。我們已經(jīng)使用它來設(shè)置每個視圖的標(biāo)題,但我們也可以使用它來設(shè)置List
.
最簡單的方法是使用 Swift 的filter()
方法。這將通過你作為閉包提供的測試運(yùn)行序列中的每個元素,并且從測試中返回 true 的任何元素都將作為新數(shù)組的一部分發(fā)回。我們的ProspectsView
已經(jīng)有一個prospects
正在傳遞的屬性,其中包含一系列人員,因此我們可以返回所有人員、所有聯(lián)系過的人或所有未聯(lián)系過的人。
將此屬性添加到ProspectsView
前兩個下面:
當(dāng)filter()運(yùn)行時
,people
數(shù)組中的每個元素都
會
通過我們的測試。所以,$0.isContacted
意思是“當(dāng)前元素的isContacted
屬性是否設(shè)置為真?”?數(shù)組中通過該測試的所有項目(isContacted
已
設(shè)置為 true)都將被添加到新數(shù)組并從filteredResults
返回
.?而當(dāng)我們使用!$0.isContacted
時,
我們會得到相反的結(jié)果:只包括尚未聯(lián)系的潛在客戶。
有了這個計算屬性,我們現(xiàn)在可以創(chuàng)建一個List
遍歷該數(shù)組的循環(huán)。這將使用 VStack
顯示每個潛在客戶的標(biāo)題和電子郵件地址
,我們還將使用ForEach
以便稍后添加刪除。
用這個替換現(xiàn)有的文本視圖ProspectsView
:
如果你再次運(yùn)行該應(yīng)用程序,你會發(fā)現(xiàn)情況開始好轉(zhuǎn)了。
在我們繼續(xù)之前,我希望你考慮一下:既然我們正在使用計算屬性,SwiftUI 如何知道在屬性更改時刷新視圖?答案其實(shí)很簡單:不是。
當(dāng)我們向 @EnvironmentObject
中添加一個
屬性時,我們還要求 SwiftUI在該屬性發(fā)生更改時重新調(diào)用該ProspectsView
的
body
屬性。
因此,每當(dāng)我們將一個新人插入到數(shù)組中時,people
的@Published
屬性包裝器將向所有正在觀察它的視圖宣布更新,并且 SwiftUI 將重新調(diào)用ProspectsView
的
.?這反過來又會再次計算我們的計算屬性,因此body
List
會發(fā)生變化。
我喜歡 SwiftUI 在這里透明地為我們承擔(dān)這么多工作的方式,這意味著我們可以專注于我們?nèi)绾芜^濾和呈現(xiàn)我們的數(shù)據(jù),而不是如何連接所有管道以確保事情保持最新。


