屬性包裝、投射屬性
- 由
@修飾的屬性稱為屬性包裝(Property Wrapper)。 @State、@Binding、@ObjectBinding、@EnvironmentObject都是@propertyWrapper修飾的struct。- 對一個由
@修飾的屬性,在它前面使用$取得的值,被稱為投射屬性(Projection Property)。 - 並不是所有的
@屬性都提供$的投射訪問方式。
@State
@State修飾的屬性會被自動轉換為一對setter和getter- 對這個屬性進行賦值的操作,它的
body會被再次調用,進行View的刷新。 - 但由於是
value type,在不同物件傳遞時透過複製值,因此經過不同層級底層改變並無法更新頂層的值。 - 訪問
@State修飾的屬性,所有調用觸發的都是wrappedValue。 - 使用
$訪問屬性,取得的是projectedValue,為一個Binding類型的值。
SwiftUI 中 State 定義的關鍵部分
@propertyWrapper
public struct State<Value>: DynamicViewProperty, BindingConvertible {
public var value: Value { get nonmutating set }
public var wrappedValue: Value { get nonmutating set }
public var projectedValue: Binding<Value> { get }
init(initialValue value: Value)
}
範例:點擊按鈕數字累加點擊次數
struct ContentView: View {
@State private var count: Int = 0
var body: some View {
Button {
self.count += 1
} label: {
Circle()
.frame(width: 150, height: 150, alignment: .center)
.foregroundColor(.blue)
.overlay {
Text("\(count)")
.font(.system(size:72, weight: .bold))
.foregroundColor(.white)
}
}
}
}
@Binding
- 和
@State類似,也是對屬性的修飾,但將value type轉換為reference type。 - 對
@Binding修飾的屬性進行賦值,改變的是其參考,因此可以在不同物件傳遞。
範例:同上(將按鈕定義為CounterButton)
CounterButton中宣告count變數以@Binding修飾,在ContentView以projectedValue將$counter傳遞至CounterButton,當點擊按鈕count值改變,畫面改變,counter亦跟著改變。
print(counter) 印出結果皆為 1, 2, 3…
- 若
CounterButton中宣告count變數以@State修飾,在ContentView以wrappedValue將counter傳遞至CounterButton,當點擊按鈕count值改變,畫面改變,但counter不會跟著改變。
print(counter) 印出結果皆為 0
struct ContentView: View {
@State private var counter: Int = 0
var body: some View {
CounterButton(count: $counter) {
print(counter)
}
}
}
struct CounterButton: View {
@Binding var count: Int
var body: some View {
Button {
self.count += 1
} label: {
Circle()
.frame(width: 150, height: 150, alignment: .center)
.foregroundColor(.blue)
.overlay {
Text("\(count)")
.font(.system(size:72, weight: .bold))
.foregroundColor(.white)
}
}
}
}