一、@State
和一般的存儲屬性不同,@State 修飾的值,在 SwiftUI 內(nèi)部會被自動轉(zhuǎn)換為一對 setter 和 getter,對這個屬性進行賦值的操作將會觸發(fā) View 的刷新,它的 body 會被再次調(diào)用,底層渲染引擎會找出界面上被改變的部分,根據(jù)新的屬性值計算出新的 View,并進行刷新。
- 1、基本使用
struct ContentView: View {
@State var count = "eeer"
var body: some View {
VStack{
Text(count)
Button("點我") { //添加一個按鈕,指定標題文字為 First button
self.count = "1234567890"
}.background(.red)
}
}
}
注意ContentView是一個結(jié)構(gòu)體,它可以被創(chuàng)建為一個常量。如果你回想一下你學習結(jié)構(gòu)體的時候,那意味著它是不可變的——我們不能自由地改變它的值。 當創(chuàng)建想要更改屬性的結(jié)構(gòu)體方法時,我們需要添加mutating關(guān)鍵字:mutating func doSomeWork(),例如。然而,Swift不允許我們創(chuàng)建可變計算屬性,這意味著我們不能編寫mutating var body: some View——這是不允許的。幸運的是,Swift為我們提供了一個稱為屬性包裝器的特殊解決方案:我們可以在屬性之前放置一個特殊的屬性,有效地賦予它們超能力。在存儲簡單的程序狀態(tài)(如按鈕被點擊的次數(shù))的情況下,我們可以使用SwiftUI中名為@state的屬性包裝器。@State允許我們繞過結(jié)構(gòu)體的限制:我們知道不能更改它們的屬性,因為結(jié)構(gòu)是固定的,但是@State允許SwiftUI將該值單獨存儲在可以修改的地方。
上面代碼運行一下:self.count重新賦值,會觸發(fā)View刷新,Text顯示的文字將會變?yōu)椋?234567890
- 2、值傳遞
例一
struct ContentView: View {
@State var count = "eeer"
var body: some View {
VStack{
MapView(count1: count)
}
Text(count)
.padding()
}
}
struct MapView: View{
@State var count1: String
var body: some View {
Button("點我") { //添加一個按鈕,指定標題文字為 First button
self.count1 = "1234567890"
}
}
}
MapView從父視圖傳遞count值給內(nèi)部count1,是值類型的傳遞。正如普通的Swift函數(shù)傳遞參數(shù)一樣,是值的拷貝,修改MapView的count1,不會影響ContentView的count值。
例二
struct Item {
var name = "1"
var age = "10"
}
struct ContentView: View {
@State var item = Item()
var body: some View {
VStack{
Text(item.name)
.padding()
Button("點我") { //添加一個按鈕,指定標題文字為 First button
item.name = "測試"
}
}
}
}
這里Item是結(jié)構(gòu)體,本來就是值類型就可以直接修改Item的成員屬性
例三
class Item {
var name = "1"
var age = "10"
}
struct ContentView: View {
@State var item = Item()
var body: some View {
VStack{
Text(item.name)
.padding()
Button("點我") { //添加一個按鈕,指定標題文字為 First button
item.name = "測試"
}
}
}
}
注意這里的Item是個類對象,我們知道類對象一般是引用類型,這里雖然@State可以修飾 item 對象,編譯器不會報錯。但是你點擊修改 item.name = "測試"時,運行一下Text顯示的值不會有任何變化。說明@State修飾引用對象,修改值是無效的。
二、@Binding
和 @State 類似,@Binding 也是對屬性的修飾,它做的事情是將值語義的屬性“轉(zhuǎn)換”為引用語義。對被聲明為 @Binding 的屬性進行賦值,改變的將不是屬性本身,而是它的引用,這個改變將被向外傳遞.
上面2-例1的例子是值傳遞,假如我們想實現(xiàn)引用傳遞,就可以使用@Binding解決這個問題。
struct ContentView: View {
@State var count = "eeer"
var body: some View {
VStack{
MapView(count1: $count)
}
Text(count)
.padding()
}
}
struct MapView: View{
@Binding var count1: String
var body: some View {
Button("點我") { //添加一個按鈕,指定標題文字為 First button
self.count1 = "1234567890"
}
}
}
- @Binding將 count1的引用改變成向外傳遞
- 在傳遞 count時,我們在它前面加上美元符號 $。
在 Swift 中,對一個由 @ 符號修飾的屬性,在它前面使用 $ 所取得的值,被稱為投影屬性 (projection property)。有些 @ 屬性,比如這里的 @State 和 @Binding,它們的投影屬性就是自身所對應(yīng)值的 Binding 類型。不過要注意的是,并不是所有的 @ 屬性都提供 $ 的投影訪問方式。
這里運行一下改變self.count1 = "1234567890",ContentView的count會隨著一起改變,此時Text顯示1234567890
三、屬性包裝
在 SwiftUI中,像@State,@Binding這樣對屬性進行修飾,稱之為屬性包裝 (Property Wrapper)。
四、@ObservedObject
如果說 @State 是全自動駕駛的話,ObservableObject 就是半自動,它需要一些額外的聲明。ObservableObject 協(xié)議要求實現(xiàn)類型是 class,它只有一個需要實現(xiàn)的屬性:objectWillChange。在數(shù)據(jù)將要發(fā)生改變時,這個屬性用來向外進行“廣播”,它的訂閱者 (一般是 View 相關(guān)的邏輯) 在收到通知后,對 View 進行刷新。
創(chuàng)建 ObservableObject 后,實際在 View 里使用時,我們需要將它聲明為 @ObservedObject。這也是一個屬性包裝,它負責通過訂閱 objectWillChange 這個“廣播”,將具體管理數(shù)據(jù)的 ObservableObject 和當前的 View 關(guān)聯(lián)起來。
final class Person:ObservableObject{
@Published var name = "張三"
}
struct ContentView: View {
@ObservedObject var p = Person()
var body: some View {
VStack{
Text(p.name)
.padding()
Button("點我") { //添加一個按鈕,指定標題文字為 First button
p.name = "1234567890"
}
}
}
}
- @ObservedObject修飾的必須是遵守ObservableObject 協(xié)議的class對象
- class對象的屬性只有被@Published修飾時,屬性的值修改時,才能被監(jiān)聽到。
五、@EnvironmentObject
在 SwiftUI 中,View 提供了 environmentObject(_:) 方法,來把某個 ObservableObject 的值注入到當前 View 層級及其子層級中去。在這個 View 的子層級中,可以使用 @EnvironmentObject 來直接獲取這個綁定的環(huán)境值。
final class Person:ObservableObject{
@Published var name = "測試測試測試測試測試測試"
}
struct ContentView: View {
var body: some View {
VStack{
let p = Person()
MapView().environmentObject(p)
}
}
}
struct MapView: View {
@EnvironmentObject var p:Person
var body: some View {
VStack{
Text(p.name)
Button("點我") { //添加一個按鈕,指定標題文字為 First button
p.name = "1234567890"
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
@EnvironmentObject 修飾器是針對全局環(huán)境的。通過它,我們可以避免在初始 View 時創(chuàng)建 ObservableObject, 而是從環(huán)境中獲取 ObservableObject
可以看出我們獲取 p這個 ObservableObject 是通過 @EnvironmentObject 修飾器,但是在入口需要傳入 .environmentObject(p) 。@EnvironmentObject 的工作方式是在 Environment 查找 Person 實例。
六、@StateObject
StateObject行為類似ObservedObject對象,區(qū)別是StateObject由SwiftUI負責針對一個指定的View,創(chuàng)建和管理一個實例對象,不管多少次View更新,都能夠使用本地對象數(shù)據(jù)而不丟失
import SwiftUI
final class Person:ObservableObject{
@Published var name = "測試"
deinit{
print("銷毀")
}
}
struct ContentView: View {
@StateObject var p = Person()
var body: some View {
VStack{
Text(p.name)
Button("點擊"){
p.name = "哈哈哈"
}
}
}
}
@StateObject和@ObservedObject區(qū)別:
- 1、@ObservedObject只是作為View的數(shù)據(jù)依賴,不被View持有,View更新時ObservedObject對象可能會被銷毀
適合數(shù)據(jù)在SwiftUI外部存儲,把@ObservedObject包裹的數(shù)據(jù)作為視圖的依賴,比如數(shù)據(jù)庫中存儲的數(shù)據(jù)` - 2、針對引用類型設(shè)計,當View更新時,實例不會被銷毀,與State類似,使得View本身擁有數(shù)據(jù)
例子1:
import SwiftUI
final class Person:ObservableObject{
@Published var name = 1
deinit{
print("銷毀")
}
}
struct ContentView: View {
@State var count = 0
var body: some View {
VStack{
Text("刷新CounterView計數(shù) :\(count)")
Button("刷新"){
count += 1
}
MapView()
}
}
}
struct MapView: View {
@ObservedObject var p = Person()
var body: some View {
VStack{
Text("\(p.name)")
Button("+1") { //添加一個按鈕,指定標題文字為 First button
p.name += 1
}
}
}
}
- 點擊刷新時,Person 的deinit方法被調(diào)用,說明p對象被銷毀;
先連續(xù)點擊+1,Text上的數(shù)字在一直遞增,當點擊刷新時Text上的數(shù)字恢復為1,這個現(xiàn)象也說明p對象被銷毀
例子2
import SwiftUI
final class Person:ObservableObject{
@Published var name = 1
deinit{
print("銷毀")
}
}
struct ContentView: View {
@State var count = 0
var body: some View {
VStack{
Text("刷新CounterView計數(shù) :\(count)")
Button("刷新"){
count += 1
}
MapView()
}
}
}
struct MapView: View {
@StateObject var p = Person()
var body: some View {
VStack{
Text("\(p.name)")
Button("+1") { //添加一個按鈕,指定標題文字為 First button
p.name += 1
}
}
}
}
- 和例1不同的是怎么操作,p都不會銷毀
小結(jié):
@StateObject的聲明周期與當前所在View生命周期保持一致,即當View被銷毀后,StateObject的數(shù)據(jù)銷毀,當View被刷新時,StateObject的數(shù)據(jù)會保持;而ObservedObject不被View持有,生命周期不一定與View一致,即數(shù)據(jù)可能被保持或者銷毀;