屬性包裝、投射屬性
- 由 @修飾的屬性稱為屬性包裝(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)
                }
        }
    }
}