Hacking with iOS: SwiftUI Edition 里程碑(一)之 Key points

Key points – 要點

有三個要點值得更詳細地討論。這在一定程度上是在回顧我們所學到的東西——用不同的例子重新審視一遍,以確保它們是清楚的——但我也希望藉此機會回答一些迄今可能出現的問題。

Structs vs Classes – 結構體 VS 類

首先,有一些你應該記憶猶新的東西:結構體和類。這兩種方法都可以讓我們使用屬性和方法構建複雜的數據類型,但它們的工作方式(更具體地說,它們的不同之處)很重要。

如果您還記得,結構體和類之間有五個關鍵區別:

1.結構體默認有一個自動實現的成員初始化器類沒有需要自己實現。 2.類可以使用繼承來構建功能;結構不能。 3.如果複製一個類,兩個副本都指向同一個數據;結構體的副本總是唯一的。 4.類可以有反初始化器;結構體沒有。 5.可以更改常量類中的變數屬性;無論屬性是常量還是變數,常量結構體中的屬性都是固定的。

在蘋果最初的程式語言Objective-C中,我們幾乎什麼都用類——我們別無選擇,因為它確實融入了我們的工作方式。

在Swift,我們確實有另一個選擇,這個選擇應該基於上述因素。我之所以說「應該」,是因為經常會看到一些人不關心這些差異,所以總是使用類或結構體,而不考慮這種選擇的後果。

選擇結構體還是類取決於您和您正在解決的特定問題。不過,我想讓你考慮一下它是如何傳達你的意圖的。Donald Knuth說「程式應該是給人讀的,只是順帶讓電腦執行(programs are meant to be read by humans and only incidentally for computers to execute)」,這真的觸及了我所說的核心:當有人讀到你的程式碼時,他們對你的意圖清楚嗎?

如果您大多數時候使用結構體,那麼在一個特定情況使用類就會傳達一些意圖:這個東西是不同的,需要不同的使用方式。如果你總是使用類,這種區別就會消失——畢竟,你不太可能經常需要它們。

提示:SwiftUI的一個迷人的細節是它如何完全顛倒我們使用結構體和類的方式。在UIKit中,我們會使用struct來表示數據,使用class來表示UI,但在SwiftUI中,情況完全相反,這很好地提醒了我們學習東西的重要性,即使你認為有些東西不是立馬有用的。

使用ForEach

我想討論的第二件事是ForEach,我們已經將它用於如下程式碼:

ForEach(0 ..< 100) { number in      Text("Row (number)")  }

ForEach是一個視圖,就像SwiftUI中的大多數其他東西一樣,但它允許我們在循環中創建其他視圖。在這樣做的同時,它還允許我們繞過SwiftUI規定的10個子視圖的限制——ForEach本身成為了10個子視圖中的一個,而不是它裡面的視圖。

現在考慮這樣一個字元串數組:

let agents = ["Cyril", "Lana", "Pam", "Sterling"]

我們如何循環這些並生成文本視圖?

一種選擇是使用我們已經使用過的結構:

VStack {      ForEach(0 ..< agents.count) {          Text(self.agents[$0])      }  }

但是SwiftUI提供了另一種選擇:我們可以直接在數組上循環。這需要更多的思考,因為SwiftUI想知道如何識別數組中的每個項。

考慮一下:如果我們在一個包含四項的數組上循環,我們將創建四個視圖,但是如果body被重新調用,並且我們的數組現在包含五項,那麼SwiftUI需要知道哪個視圖是新的,這樣它就可以在UI中顯示。SwiftIUI最不想做的就是扔掉它的整個布局,每次做一個小小的改變就從頭開始。相反,它希望儘可能減少工作量:它希望保留現有的四個視圖,只添加第五個視圖。

所以,我們回到Swift如何識別數組中的值。當我們使用0..<50..<agents.count這樣的範圍時,Swift確信每個項目都是唯一的,因為它將使用範圍中的數字——每個數字在循環中只使用一次,所以它肯定是唯一的。

在我們的字元串數組中,這是不可能的,但是我們可以清楚地看到每個值都是唯一的:在[「Cyril」,「Lana」,「Pam」,「Sterling」]中的值不會重複。所以,我們可以告訴SwiftUI字元串本身——「Cyril」、「Lana」等可以用來唯一地標識循環中的每個視圖。

我們可以在程式碼裡面這樣寫:

VStack {      ForEach(agents, id: .self) {          Text($0)      }  }

因此,我們現在直接讀取數組中的項,而不是在整數上循環並使用它讀入數組,就像for循環一樣。

隨著您使用SwiftUI的進展,我們將研究識別視圖的第三種方法——使用Identifiable協議。

使用綁定

當我們使用PickerTextField等控制項時,我們使用$propertyName為它們創建到某種@State屬性的雙向綁定。這對簡單的屬性非常有用,但是有時候——只是有時候,希望如此!——您可能需要更高級的東西:如果您想運行一些邏輯來計算當前值,該怎麼辦?或者,如果你不只是想在寫的時候隱藏一個值呢?

如果我們想對綁定中的更改做出反應,我們可能會嘗試利用Swift的didSet屬性觀察,但您會失望的。這就是自定義綁定的來源:它們可以像@State綁定一樣使用,但我們無法完全控制它們的工作方式。

SwiftUI為我們做的每件事都可以手工完成,儘管依賴自動解決方案幾乎總是更好的,但在窺視一下幕後確實會有幫助,這樣你就能了解它在為你做什麼。

首先,讓我們看看最簡單的自定義綁定形式,它將值存儲在另一個@State屬性中,並將其返回:

struct ContentView: View {      @State var selection = 0        var body: some View {          let binding = Binding(              get: { self.selection },              set: { self.selection = $0 }          )            return VStack {              Picker("Select a number", selection: binding) {                  ForEach(0 ..< 3) {                      Text("Item ($0)")                  }              }.pickerStyle(SegmentedPickerStyle())          }      }  }

因此,該綁定實際上只是作為一個傳遞——它本身不存儲或計算任何數據,而是作為UI和被操縱的底層狀態值之間的一個填充。

但是,請注意,選擇器現在是使用selection:binding進行的,不需要美元符號。我們不需要在這裡明確要求雙向綁定,因為它已經是了。

如果我們願意的話,我們可以創建一個更高級的綁定,它不僅僅傳遞一個值。例如,假設我們有一個帶有三個切換開關的表單:用戶是否同意條款和條件,同意隱私策略,並同意接收有關發貨的電子郵件。

我們可以將其表示為三個布爾型的@State屬性:

@State var agreedToTerms = false  @State var agreedToPrivacyPolicy = false  @State var agreedToEmails = false

儘管用戶可以手動切換它們,但我們可以使用自定義綁定一次完成所有操作。如果這三個布爾值都為真,則此綁定將為真,但如果更改了綁定,則它將更新所有布爾值,如下所示:

let agreedToAll = Binding(      get: {          self.agreedToTerms && self.agreedToPrivacyPolicy && self.agreedToEmails      },      set: {          self.agreedToTerms = $0          self.agreedToPrivacyPolicy = $0          self.agreedToEmails = $0      }  )

所以現在我們可以創建四個切換開關:這三個選擇都使用布爾值表示同意或不同意,一個同時同意或不同意所有三個的控制開關:

struct ContentView: View {      @State var agreedToTerms = false      @State var agreedToPrivacyPolicy = false      @State var agreedToEmails = false        var body: some View {          let agreedToAll = Binding<Bool>(              get: {                  self.agreedToTerms && self.agreedToPrivacyPolicy && self.agreedToEmails              },              set: {                  self.agreedToTerms = $0                  self.agreedToPrivacyPolicy = $0                  self.agreedToEmails = $0              }          )            return VStack {              Toggle(isOn: $agreedToTerms) {                  Text("Agree to terms")              }                Toggle(isOn: $agreedToPrivacyPolicy) {                  Text("Agree to privacy policy")              }                Toggle(isOn: $agreedToEmails) {                  Text("Agree to receive shipping emails")              }                Toggle(isOn: agreedToAll) {                  Text("Agree to all")              }          }      }  }

再說一遍,訂製綁定並不是你經常想要的,但是花點時間看看幕後,了解發生了什麼是非常重要的。儘管它非常聰明,但它只是一個工具,而不是魔法!

譯自Milestone: Projects 1-3 Key points