用狀態設計 SwiftUI 畫面 — 認識 State property
SwiftUI 用狀態決定如何顯示和更新畫面,它的很多 UI 元件都跟狀態有關,幫助我們開發 App 時更容易實現資料和畫面的同步。接下來我們就以下雨時顯示孫燕姿的壞天氣,放晴時顯示盧廣仲的我愛你歌詞為例,說明狀態如何深深影響著 SwiftUI 的畫面。
設計 App 畫面
struct ContentView: View {
var isRain = true
var body: some View {
VStack {
if isRain {
Image(systemName: "cloud.rain.fill")
.resizable()
.frame(width: 100, height: 100)
Text("我們淋著大雨不知何時才能放晴")
} else {
Image(systemName: "sun.max.fill")
.resizable()
.frame(width: 100, height: 100)
Text("太陽公公出來了,他對我呀笑呀笑")
}
Button("今天天氣如何 ?") {
}
}
}
}
說明。
- 宣告型別 Bool 的變數 isRain 判斷是否下雨,利用它決定要顯示的圖片和文字。
- 點擊 button 將決定今天是否下雨。
將 if else 改成 ? : (ternary conditional operator)
實作點擊 button 決定是否下雨前,我們先將程式改成 ? : 的寫法。SwiftUI 的程式經常使用 ?:
來讓程式變得更精簡。
struct ContentView: View {
var isRain = true
var body: some View {
VStack {
Image(systemName: isRain ? "cloud.rain.fill" : "sun.max.fill")
.resizable()
.frame(width: 100, height: 100)
Text(isRain ? "我們淋著大雨不知何時才能放晴" : "太陽公公出來了,他對我呀笑呀笑")
Button("今天天氣如何 ?") {
}
}
}
}
透過 random 決定是否下雨
在 button 點選執行的 closure 裡透過 random 決定是否下雨。
Button("今天天氣如何 ?") {
isRain = .random()
}
此時將馬上產生紅色錯誤,Cannot assign to property: self is immutable。
為什麼會錯呢 ? 因為 SwiftUI 的 view 通常以 struct 定義,就像剛剛的 struct ContentView。而 struct 是 value type,因此我們無法在 computed property 的 getter & function 裡修改 stored property。就像以下的 Baby 以 struct 定義,function getOlder 修改 property age 將產生類似的錯誤。(註: 不過我們可在加了 mutating 的 function 和 computed property 的 setter 修改 stored property。)
以 @State 宣告可改變的 property
在 property isRain 前加上 @State
,即可修正剛剛的錯誤。(註: 一般我們宣告 @State property 時會再加上 private,讓程式更安全,表示我們只會在定義它的型別裡存取它。)
@State private var isRain = true
因此在 SwiftUI view 型別的定義裡,若想讓它的 property 能夠改變,不只要以 var 宣告成變數,還要記得加上 @State
。
加了 @State
後,除了變數可以改變,還會有神奇的副作用,每當我們點選 button,觸發 button 的 closure 程式修改 isRain 時,畫面也會更新顯示目前的天氣內容。
當我們在 ContentView 的 property 前加上 @State 時,SwiftUI 將認為此 property 代表某種影響畫面內容的狀態。當狀態改變時,畫面將馬上更新,重新生成 body 裡受 state property 影響的元件。
@State private var isRain = true
表示是否下雨的狀態,當我們在 button 點選時透過 isRain = Bool.random()
修改它時, computed property body 的程式將重新執行產生新的畫面,顯示晴天或雨天的圖片文字。
以 @State 宣告的 property 有個重要的特性,只要它的內容改變,畫面也會立即更新。它帶來了以下兩個好處。
- 不用另外寫 property 內容改變時更新畫面的程式。
- 不用擔心畫面顯示的內容跟 property 的內容不同步,比方修改了 property,但卻忘了更新畫面。
也許有人好奇,以 struct 定義的 ContentView 是 value type,為何加了 @State 就能修改它的 property ? 這是因為加了 @State 後,SwiftUI 將在背後另外產生空間儲存 property 的內容,它不再儲存在 ContentView 裡,因此我們可以修改它的內容。
ps: @State 實現的機制跟 Swift 的 property wrapper 有關,有興趣的朋友也可參考以下連結的說明。
完整程式
struct ContentView: View {
@State private var isRain = true
var body: some View {
VStack {
Image(systemName: isRain ? "cloud.rain.fill" : "sun.max.fill")
.resizable()
.frame(width: 100, height: 100)
Text(isRain ? "我們淋著大雨不知何時才能放晴" : "太陽公公出來了,他對我呀笑呀笑")
Button("今天天氣如何 ?") {
isRain = .random()
}
}
}
}
現在可以開始測試 App 了。當我們點選今天天氣如何,isRain 將隨機變成 true 或 false,當它為 true 時畫面將顯示雨天,false 時畫面將顯示晴天。
以狀態設計 SwiftUI App
了解 SwiftUI state 的作用後,我們未來設計 SwiftUI 畫面時,可以試著多用狀態的角度設計畫面,思考畫面會有哪些狀態以及每個狀態呈現的模樣,將內容影響畫面的變數常數加上 @State,讓資料和畫面自動保持同步。
練習: 結合圖片、文字、button 的 SwiftUI State 練習題
視情況聰明更新的 SwiftUI body
也許有人會擔心,當畫面比較複雜時,property body 裡將生成很多 UI 元件,如果常常更新重新執行 body 裡的程式,豈不是會大大影響 App 的效能 ?
其實 SwiftUI 是很聰明的,它會判斷此次更新將影響哪些元件,只會重新產生那些受影響的元件,相關說明可參考以下連結。
SwiftUI @State 的進階解析
影響 SwiftUI 畫面更新的 Equatable & function ==