SwiftUI學習100天(Day47 - 里程碑:項目7-9)

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

你現(xiàn)在已經掌握了一些重量級技能,所以在我們繼續(xù)之前,是時候回顧一下所涵蓋的內容,詳細介紹一些主題,并迎接新的挑戰(zhàn)。

恭喜你完成了另外三個項目!在我們的繪圖技術項目之后,你可能會感到疲倦,但今天和明天應該是一個很好的節(jié)奏變化——今天是鞏固的一天,明天會有所不同。
今天的挑戰(zhàn)很有趣,老實說,如果你有時間的話,它有很大的潛力可以開發(fā)成更大的應用程序。像今天這樣的日子很重要,因為它們給了你一個完全在你掌握之中的想法,并給了你時間和范圍來執(zhí)行它。希望你能充分利用這一點——正如宇航員梅杰米森曾經說過的那樣,“我喜歡將想法視為潛在的能量:它們真的很棒,但除非我們冒險將它們付諸行動,否則什么都不會發(fā)生?!?/p>
所以,今天是行動日:你前面有很多編碼,如果你想進一步推進項目,還有更多編碼的潛力。讓我們開始吧!
今天你有三個主題要完成,其中之一是你的挑戰(zhàn)。

你學到了什么
希望你覺得這些項目開始對你有所幫助,不僅可以進一步提高你的 SwiftUI 技能,還可以教你一些更高級的 Swift。此外,當然,你還有兩個新構建的 SwiftUI 項目——你可以繼續(xù)自定義所有你想要的項目,將它們放在 GitHub 上,或者將它們轉換成更適合你口味的其他項目。
以下是我們在過去三個項目中涵蓋的所有新事物的快速回顧:
為什么
@State
只適用于結構。如何使用
@ObservedObject
類工作。@Published
如何
讓我們宣布正在觀察的任何 SwiftUI 視圖的屬性更改。使用sheet()
修飾符和dismiss
環(huán)境鍵呈現(xiàn)和關閉視圖。使用
onDelete(perform:)
啟用滑動刪除。添加
EditButton
到導航欄項目,讓用戶更輕松地編輯列表數(shù)據(jù)。使用
UserDefaults
讀取和寫入數(shù)據(jù)
。使用
Codable
歸檔和取消歸檔數(shù)據(jù)
,包括處理存儲在層次結構中的數(shù)據(jù)。使用
Identifiable
協(xié)議來確保所有項目都可以在我們的用戶界面中被唯一標識。如何使用
GeometryReader
使內容適合屏幕。ScrollView
用于在可滾動
區(qū)域中布置自定義視圖。使用
NavigationLink
將新視圖推送到導航堆棧
。使用 Swift 的泛型系統(tǒng)編寫處理不同類型數(shù)據(jù)的方法。
創(chuàng)建自定義路徑和形狀。
使用
InsettableShape
創(chuàng)建可以插入并描邊邊框的形狀
。用于
CGAffineTransform
創(chuàng)建旋轉和平移。使用
ImagePaint
制作創(chuàng)意邊框和填充
。啟用 Metal 以使用
drawingGroup()
.修改混合模式和飽和度。
使用animatableData
和AnimatablePair
對形狀進行動畫處理
。
我希望你會很認同這些,而且范圍很廣——我們已經從硬核語言特性到面向用戶的視圖,甚至更進一步到 Swift 繪圖系統(tǒng)的創(chuàng)造性使用。有些人會更喜歡純語言的東西,而另一些人會更喜歡編碼的更有創(chuàng)意的一面,這沒關系——我們的學習方式都不一樣!



關鍵點
雖然我們在前三個項目中涵蓋了很多內容,但我想更詳細地介紹三件具體的事情。別擔心——繪畫不是其中之一!
類與結構:有什么區(qū)別,為什么重要?
Swift 為我們提供了兩種創(chuàng)建我們自己的復雜數(shù)據(jù)類型的方法,理解我們?yōu)槭裁匆褂盟鼈円约盀槿魏谓o定任務選擇哪種方法很重要。
類和結構的根本區(qū)別在于,一個是值類型,另一個是引用類型。這些是我們如何處理數(shù)據(jù)的標準編程術語:數(shù)據(jù)只是一個簡單的值,例如“Hello”或 5,還是僅僅是一個路標,上面寫著“我的數(shù)據(jù)存儲在 RAM 中的這個位置”。
一旦你理解了這種差異,結構和類就變成了兩種截然不同的東西,但是當你學習這些差異時,你會覺得它們根本沒有什么不同??梢赃@樣想:當我們創(chuàng)建一個包含結構的變量時,該數(shù)據(jù)實際上存儲在變量中。相比之下,當我們使用一個類時,數(shù)據(jù)被放在內存中的某個地方,并且變量持有一個長數(shù)字來標識該內存的位置。
這就是這個名字的由來:“引用類型”被存儲為對某處內存的引用,有點像路標。它不是一個直接指向我家的變量,而是指向一個指向我家的路標——有一個額外的間接層。這就是為什么如果你將兩個或多個變量指向一個類的同一個實例,它們可以修改相同的數(shù)據(jù):你只有幾個路標都指向同一個房子。
這也是為什么引用類型和值類型在用作常量時表現(xiàn)不同的原因。如果我們創(chuàng)建一個類的常量實例,我們所做的就是創(chuàng)建一個常量路標——我們說過“這個路標總是指向門牌號 24601,不能指向不同的房子?!?然而,這并不能阻止我們改造房子:也許我們想加一層樓,或者改變廚房,或者甚至可能完全拆除房子并建造新的。如果你想讓這些東西固定——如果你想讓實際的房子本身保持不變——那么你需要為你的類使用常量屬性。
因此,我們可以在擁有可變數(shù)據(jù)?(let myHouse = House()
?) 的同時制作一個不變的路標 (?var numberOfFloors = 3
)。但我們也可以反過來:我們可以制作一個可變路標(var myHouse = House
)的同時讓他
具有常量數(shù)據(jù)?(?let numberOfFloors = 3
),并且它的行為非常不同:我們可以移動路標,使其指向不同的房屋,但我們不能改造房子自己。
現(xiàn)在想想所有這些與 Swift、SwiftUI 甚至 UIKit 有何關系。如果你的應用程序中有三個屏幕,所有這些屏幕都共享相同的數(shù)據(jù),那么確保數(shù)據(jù)在后臺(所有變量包含相同的值)和給用戶展示的界面(我們所有的列表/文本視圖?/ etc 顯示相同的值)保持同步是很重要的。
SwiftUI 提供了諸如@State
和之類的包裝器,@ObservedObject
以確保我們的視圖在數(shù)據(jù)更改時保持更新,但是這些包裝器無法與 UIKit 一起使用——你需要自己響應更改,然后更新用戶界面以反映這些更改。
這產生了一個問題:
視圖 A 可以創(chuàng)建一個類的實例。
視圖 A 可以將其傳遞給視圖 B,以便他們共享它。
然后視圖 B 可以更改數(shù)據(jù)并更新其 UI。
視圖 A 不會知道數(shù)據(jù)已更改,并且會顯示舊的 UI。
因此,UIKit 開發(fā)人員通常將結構用于數(shù)據(jù),因為這意味著每個視圖都有自己的數(shù)據(jù)副本,并且不會意外更改。更有趣的是,所有 UIKit 的視圖類型都是使用類構建的,這意味著 UIKit 開發(fā)人員將他們的視圖構建為類并使用結構來存儲他們的數(shù)據(jù)——這與 SwiftUI 完全相反。
明智地使用 UserDefaults
UserDefaults
讓我們輕松存儲少量數(shù)據(jù)——它會自動附加到我們的應用程序,這意味著它可以在我們的應用程序啟動后立即加載。雖然它非常有用(而且你會嚴重依賴它?。?,但它確實有兩個缺點:
你應該只在那里存儲少量數(shù)據(jù)——任何超過 512KB 的數(shù)據(jù)都是可怕的。
你只能輕松存儲某些類型的數(shù)據(jù);其他一切都必須首先使用
Codable
來獲取一些二進制數(shù)據(jù)。
UserDefaults
支持的類型列表
簡短而精確:字符串、數(shù)字、日期、URL 和二進制數(shù)據(jù),以及這些類型的數(shù)組和字典。不包括 URL(實際上只是花哨的字符串),所有這些都是可以存儲在 plist 文件中的相同類型——屬性列表的縮寫。
這不是巧合:UserDefaults
實際上就像我們的 Info.plist 文件一樣使用屬性列表寫出它的數(shù)據(jù)。事實上,記住這個鏈接真的可以幫助你充分利用UserDefaults
——如果我們的 Info.plist 文件包含 100,000 個數(shù)據(jù)條目,把 100,000 個項目放在UserDefaults
,那會很奇怪。
因此,按照其設計目的使用UserDefaults
系統(tǒng)——正如 Apple 自己的文檔所說,它被稱為用戶默認值,“因為它們通常用于確定應用程序在啟動時的默認狀態(tài)或默認情況下的行為方式。”
何時使用泛型
我們使用泛型來創(chuàng)建一種解碼方法,該方法能夠從應用程序包中獲取任何 JSON 文件并加載到我們選擇的Codable
類型中。但是——這是一個很大的但是!– 我們首先將方法編寫為非泛型:如果你還記得的話,它最初解碼了一組宇航員,然后升級為加載任何Codable
類型。
那不是我在浪費你的時間,而是向你介紹了一種思考泛型和協(xié)議的明智方法。在這個項目中,我們需要解碼來自?astronauts.json 中的Astronaut
實例數(shù)組,因此我們編寫了一個方法來精確地做到這一點——沒有協(xié)議也沒有泛型,只是一個簡單的擴展Bundle
來幫助保持代碼的組織性。這模仿了我們大腦的思維方式:我們可以理解像宇航員這樣的具體事物,并且可以很容易地描述它們。
但是,對于協(xié)議和泛型,事情并不是那么簡單——我們現(xiàn)在有一系列可能的類型可以使用,除了遵循相同的協(xié)議之外,它們可能完全無關。例如,整數(shù)和字符串符合 Swift 的內置Comparable
協(xié)議,這就是為什么 Swift 知道如何對它們的數(shù)組進行排序,但除此之外它們是完全不同的東西。
也許令人困惑的是,我們無法比較兩個可比較的對象,事實上,即使試圖從方法中返回Comparable
也行不通。如果你不相信我,請嘗試一下:
這不會編譯,并且有充分的理由:Comparable
它本身并不意味著什么。正如我所說,字符串和整數(shù)都符合Comparable
協(xié)議,但這意味著你可以將一個整數(shù)與另一個整數(shù)進行比較,而不是你可以將任何Comparable
類型與另一種類型進行比較——這沒有任何意義。
這就是通用約束如此有用的原因:它們讓我們說“這可以是任何類型的對象,只要……”然后提供一些限制。而且——也許違反直覺——添加限制通常會啟用更多功能。如你所見,當我們說我們的解碼方法可以用于任何類型時,這意味著我們不能使用JSONDecoder
;在我們明確添加Codable
限制之前,Swift 無法知道它可以安全地將 JSON 解碼為該類型。
所以,用好泛型的關鍵是一開始不要使用它們,當你確實需要它們時添加限制,這樣你就可以獲得盡可能多的功能。



挑戰(zhàn)
在我們繼續(xù)下一批項目之前,你需要完成一個新的挑戰(zhàn)。這意味著使用你在前三個項目中獲得的技能,自己從頭開始構建一個完整的應用程序。
這次你的目標是為那些想要跟蹤他們做了多少事情的人開發(fā)一個習慣跟蹤應用程序。這可能是學習一門語言、練習一種樂器、鍛煉身體等等——他們可以決定添加哪些活動,并根據(jù)需要進行跟蹤。
至少,這意味著應該有一個他們想要跟蹤的所有活動的列表,以及一個用于添加新活動的表格——一個標題和描述就足夠了。
對于更大的挑戰(zhàn),點擊其中一項活動應該會顯示帶有描述的詳細信息屏幕。對于艱巨的挑戰(zhàn)——請參閱下面的提示!– 使該詳細信息屏幕包含他們完成的次數(shù),以及一個增加完成次數(shù)的按鈕。
如果你想讓該應用程序真正有用,請使用Codable
并使用UserDefaults
加載和保存你的所有數(shù)據(jù)。
因此,此應用程序分為三個級別,你可以根據(jù)自己有多少時間以及想要推動自己走多遠來選擇想要走多遠。不過,我確實建議你至少嘗試每個級別——你獲得的每一點練習都有助于鞏固你的學習!

提示:
從你的數(shù)據(jù)開始:定義一個包含單個活動的結構,以及一個包含一系列活動的類。
該類將需要符合
ObservableObject
并給屬性使用@Published
。你的主要列表和表單都應該能夠讀取共享活動對象。
確保你的活動符合
Identifiable
以避免出現(xiàn)問題。使用
sheet()
顯示你的添加表單
,使用NavigationLink
顯示你的活動詳細信息視圖(如果你添加了一個)
。
制作增加完成計數(shù)的按鈕會給你帶來挑戰(zhàn),因為你需要修改傳入的活動。如果你遇到困難,最簡單的方法是:
讓你的結構符合
Equatable
.?你在這里不需要任何特別的東西——只需在Equatable
之后添加
Codable
和
Identifiable
。
將選定的活動和
ObservableObject
課程都傳遞到你的詳細信息視圖中。當點擊增量按鈕時,復制現(xiàn)有活動并將其完成計數(shù)加 1。
使用
firstIndex(of:)
查找上一個活動在類數(shù)組中的位置,然后將其更改為你的新活動 - 類似data.activities[index] = newActivity
的東西
會起作用。(這需要Equatable
步驟 1 的一致性?。?/p>
這是一個真正有用的應用程序,特別是如果它專門針對特定興趣 - 如果目標是練習樂器,那么你可以想象一個更高級的應用程序會建議不同的練習,或者如果目標是鍛煉,那么它可能會建議新的練習讓事情變得混亂。
事實上,這個挑戰(zhàn)只是一個小應用程序,但我希望它至少能讓你思考。祝你好運!



注意:如果你沒有在分配給他們的那一天完成挑戰(zhàn),請不要擔心——在未來的日子里,你會發(fā)現(xiàn)你有一些空閑時間,所以挑戰(zhàn)是你可以在未來返回的。