包裝屬性包裝器(Wrapping up property wrappers)
使用屬性包裝器(property wrappers)疯趟,我們需要注意管理的是值的狀態(tài)還是對(duì)象的狀態(tài)沸移。值主要用于描述 app 用戶界面的狀態(tài)环疼。如果我們可以使用值數(shù)據(jù)類(lèi)型對(duì) app 的數(shù)據(jù)進(jìn)行建模,那么很幸運(yùn)在讶,因?yàn)槲覀冇懈嘤糜谔幚碇档膶傩园b器選項(xiàng)蟀伸。 但在某種程度上泌类,大多數(shù) app 需要引用類(lèi)型來(lái)對(duì)其數(shù)據(jù)進(jìn)行建模,通常用于在集合中添加或刪除項(xiàng)目藤滥。
包裝值類(lèi)型(Wrapping values)
@State 和 @Binding 是值屬性包裝器的主力鳖粟。如果視圖沒(méi)有從任何父視圖接收到值,則視圖擁有該值拙绊。在這種情況下向图,它是一個(gè) @State 屬性——唯一的事實(shí)來(lái)源(source of truth)泳秀。首次創(chuàng)建視圖時(shí),它會(huì)初始化其 @State 屬性榄攀。當(dāng) @State 值改變時(shí)嗜傅,視圖會(huì)重繪自身,重置除 @State 屬性之外的所有內(nèi)容檩赢。
擁有@State 屬性的視圖可以將 @State 值作為普通只讀值或作為讀寫(xiě) @Binding 傳遞給子視圖吕嘀。
當(dāng)我們?yōu)?app 制作原型并嘗試子視圖時(shí),可能會(huì)將其編寫(xiě)為僅具有 @State 屬性的獨(dú)立視圖贞瞒。稍后偶房,當(dāng)將其放入 app 時(shí),我們只需將來(lái)自父視圖的值屬性的 @State 更改為 @Binding军浆。
我們的 app 可以訪問(wèn)內(nèi)置的 @Environment 值棕洋。環(huán)境值保留在我們附加的視圖的子樹(shù)中。通常瘾敢,這只是像 VStack 這樣的容器拍冠,我們可以在其中使用環(huán)境值來(lái)設(shè)置默認(rèn)值,如字體大小簇抵。
注意:我們還可以定義自己的自定義環(huán)境值庆杜,例如將視圖的屬性公開(kāi)給祖先視圖。
我們可以在 @AppStorage 或 @SceneStorage 字典中存儲(chǔ)一些值碟摆。@AppStorage 值在 UserDefaults 中晃财,因此它們?cè)?app 關(guān)閉后仍然存在。當(dāng)應(yīng)用重新打開(kāi)時(shí)典蜕,我們可以使用 @SceneStorage 值來(lái)恢復(fù)場(chǎng)景的狀態(tài)断盛。在 iOS 中,場(chǎng)景最容易被視為 iPad 上的多個(gè)窗口愉舔。
包裝引用類(lèi)型(Wrapping objects)
當(dāng)我們的 app 需要更改并響應(yīng)引用類(lèi)型的更改時(shí)钢猛,我們創(chuàng)建一個(gè)符合 ObservableObject 并發(fā)布適當(dāng)屬性的類(lèi)。在這種情況下轩缤,使用 @StateObject 和 @ObservedObject 的方式與 @State 和 @Binding 用于值的方式非常相似命迈。
我們將視圖中的發(fā)布者類(lèi)實(shí)例化為 @StateObject,然后將其作為 @ObservedObject 傳遞給子視圖火的。當(dāng)擁有視圖(the owning view)重繪自身時(shí)壶愤,它不會(huì)重置其 @StateObject 屬性。
如果我們的 app 的視圖需要更靈活地訪問(wèn)對(duì)象馏鹤,則可以將其提升到視圖子樹(shù)的環(huán)境(environment)中征椒,讓其類(lèi)型仍然是 @StateObject。我們必須在此處實(shí)例化它湃累。如果忘記創(chuàng)建它勃救,app 將會(huì)崩潰碍讨。然后使用 .environmentObject(_:) 修飾符將其附加到視圖。視圖子樹(shù)中的任何視圖都可以通過(guò)聲明該類(lèi)型的 @EnvironmentObject 來(lái)訂閱發(fā)布者對(duì)象剪芥。
要使 app 中的每個(gè)視圖都可以使用環(huán)境(environment)對(duì)象垄开,可以在 app 創(chuàng)建其 WindowGroup 時(shí)將其附加到根視圖。
@ViewBuilder
各種內(nèi)置視圖税肪,例如 HStack 和 VStack 通過(guò)使用 @ViewBuilder 可以顯示各種類(lèi)型的視圖溉躲。
擁有 @State 屬性的視圖可以將其值或綁定(binding)傳遞給其子視圖。如果它將綁定傳遞給子視圖益兄,則該子視圖就具有了對(duì)事實(shí)源(the source of truth)的引用锻梳。 這允許它更新該屬性的值或在該值更改時(shí)重繪自身。當(dāng) @State 值發(fā)生變化時(shí)净捅,任何引用它的視圖都會(huì)使其外觀無(wú)效并重繪自身以顯示新?tīng)顟B(tài)疑枯。
視圖是一種結(jié)構(gòu)體,因此除非將其包裝為 @State 或 @Binding 屬性蛔六,否則我們無(wú)法更改其屬性值荆永。
// 有時(shí)候我們會(huì)把一個(gè)視圖的屬性傳至子節(jié)點(diǎn)中,
// 但是又不能直接的傳遞給子節(jié)點(diǎn)国章,因?yàn)樵?Swift 中值的傳遞形式是值類(lèi)型傳遞方式具钥,
// 也就是傳遞給子節(jié)點(diǎn)的是一個(gè)拷貝過(guò)的值。
// 但是通過(guò) @Binding 修飾器修飾后液兽,屬性變成了一個(gè)引用類(lèi)型骂删,傳遞變成了引用傳遞,
// 這樣父子視圖的狀態(tài)就能關(guān)聯(lián)起來(lái)了四啰。
@Binding var selected: Set<String>
擁有@State 屬性的視圖負(fù)責(zé)初始化它宁玫。 @State 屬性包裝器為視圖結(jié)構(gòu)體之外的值創(chuàng)建持久存儲(chǔ),并在視圖重繪自身時(shí)保留其值柑晒。這意味著初始化只發(fā)生一次欧瘪。
如果 是一個(gè)類(lèi),而不是一個(gè)結(jié)構(gòu)體匙赞,則不能使用 @State 屬性包裝器恋追,應(yīng)該使用@StateObject。
@StateObject 屬性包裝器確保一個(gè)類(lèi)僅被實(shí)例化一次罚屋。 當(dāng) ContentView 重繪自身時(shí),它仍然存在嗅绸。