SwiftUI2.0 數(shù)據(jù)綁定@State,@Binding 权旷,@ObservedObject

開(kāi)發(fā)語(yǔ)言:SwiftUI 2.0
開(kāi)發(fā)環(huán)境:Xcode 12.0.1
發(fā)布平臺(tái):IOS 14

在SwiftUI中替蛉,有自己獨(dú)特的一套數(shù)據(jù)綁定機(jī)制,利用此機(jī)制構(gòu)建數(shù)據(jù)結(jié)構(gòu)后,一旦數(shù)據(jù)源發(fā)生更新躲查,SwiftUI內(nèi)部會(huì)自動(dòng)觸發(fā)畫(huà)面刷新它浅,保持?jǐn)?shù)據(jù)和界面的同步。數(shù)據(jù)綁定使用以下關(guān)鍵字:

  • @State和@Binding
  • ObservableObject協(xié)議镣煮,@ObservedObject和@Published罚缕,@StateObject(2.0新增)
  • @EnvironmentObject

這些關(guān)鍵字分別有著自己的適用場(chǎng)景,下面分別進(jìn)行介紹

1怎静、 @State和@Binding

1.1邮弹、 @State

假設(shè)有以下場(chǎng)景,View中存在一個(gè)Button蚓聘,點(diǎn)擊Button會(huì)修改Button的文字顯示腌乡,使用SwiftUI實(shí)現(xiàn)此View。

struct SubView:View {
    var content:String

    var body: some View {
        VStack{
            VStack {
                Button(action: {
                    self.content = "changed"
                }) {
                    Text(content)
                }
            }
        }
    }
}

我們期待點(diǎn)擊Button時(shí)修改content的值夜牡,但這樣使用編譯時(shí)會(huì)報(bào)錯(cuò)与纽,原因是SubView是struct,我們無(wú)法在此結(jié)構(gòu)體內(nèi)修改變量的值塘装。SwiftUI使用@State標(biāo)記解決此問(wèn)題急迂,修改后的代碼如下:

struct SubView:View {
    @State var content:String

    var body: some View {
        VStack{
            VStack {
                Button(action: {
                    self.content = "changed"
                }) {
                    Text(content)
                }
            }
        }
    }
}

由于使用了@State標(biāo)記,SwiftUI會(huì)自動(dòng)管理被標(biāo)記的屬性蹦肴,在屬性值修改后僚碎,會(huì)觸發(fā)使用此屬性的界面更新。

1.2阴幌、@Binding

延續(xù)以上例子勺阐,在新增一個(gè)ContentView。

struct ContentView: View {
    var content = "init"
    var body: some View {
        VStack{
            Text(content)
            SubView(content: content)
        }
    }
}
struct SubView:View {
    @State var content:String

    var body: some View {
        VStack{
            VStack {
                Button(action: {
                    self.content = "SubViewTap"
                }) {
                    Text(content)
                }
            }
        }
    }
}

主View中包含一個(gè)Text和子View矛双,Text顯示的內(nèi)容由content變量維護(hù)渊抽,并且傳遞content至子View,我們期待點(diǎn)擊子View中的Button時(shí)议忽,主View中Text顯示的文字也會(huì)改變懒闷。
但是運(yùn)行程序后,發(fā)現(xiàn)點(diǎn)擊Button后栈幸,只有Subview中的文本改變了愤估,原因是因?yàn)镃ontentView和SubView中的content對(duì)象不是同一個(gè)對(duì)象,在點(diǎn)擊Button后侦镇,只有Subview中的對(duì)象的值被修改了灵疮,SwiftUI使用@Binding標(biāo)記解決此問(wèn)題,修改后的代碼如下:

struct ContentView: View {
    @State var content = "init"
    var body: some View {
        VStack{
            Text(content).onTapGesture {
                self.content = "ContentViewTap"
            }
            SubView(content: $content)
        }
    }
}

struct SubView:View {
    @Binding var content:String

    var body: some View {
        VStack{
            VStack {
                Button(action: {
                    self.content = "SubViewTap"
                }) {
                    Text(content)
                }
            }
        }
    }
}


使用@Binding標(biāo)記子畫(huà)面中的content屬性壳繁,并且在構(gòu)造SubView時(shí)震捣,使用$符號(hào)將String類(lèi)型轉(zhuǎn)換為Binding<String>類(lèi)型荔棉,此時(shí),SubView持有的是主View的content的投影屬性蒿赢,無(wú)論我們通過(guò)點(diǎn)擊ContentView還是通過(guò)點(diǎn)擊SubView來(lái)修改content的值润樱,兩個(gè)View均會(huì)同步更新。

2 ObservableObject協(xié)議羡棵,@ObservedObject和@Published

現(xiàn)在將界面相關(guān)的數(shù)據(jù)封裝到Model中壹若,我們期望在點(diǎn)擊ContentView或SubView時(shí),記錄下當(dāng)前點(diǎn)擊的次數(shù)皂冰,同時(shí)修改文本的顯示/隱藏狀態(tài)店展,并且在ContentView和SubView中,同步顯示這些值秃流。

class Model {
    var clickTimes = 0
    var show = true
}

struct ContentView: View {
    @State var model = Model()
    var body: some View {
        VStack{
            Text(String(self.model.clickTimes)).onTapGesture {
                self.model.clickTimes += 1
                self.model.show.toggle()
            }
            if model.show {
                Text("ContentViewShow")
            }
            SubView(model: $model)
        }
    }
}

struct SubView:View {
    @Binding var model:Model

    var body: some View {
        VStack{
            VStack {
                Button(action: {
                    self.model.clickTimes += 1
                    self.model.show.toggle()
                }) {
                    Text(String(self.model.clickTimes))
                }
                if model.show {
                    Text("SubViewShow")
                }
            }
        }
    }
}

我們使用第一節(jié)中的@State和@Binding標(biāo)記赂蕴,來(lái)同步model,但是實(shí)際使用時(shí)舶胀,不管點(diǎn)擊ContentView還是SubView概说,界面都沒(méi)有發(fā)生改變,原因是因?yàn)辄c(diǎn)擊事件里:

self.model.clickTimes += 1
self.model.show.toggle()

我們直接修改了model中的值嚣伐,但model本身沒(méi)有發(fā)生改變糖赔,@State和@Binding只有在其關(guān)聯(lián)的變量本身發(fā)生改變后,才會(huì)觸發(fā)相應(yīng)的刷新功能轩端,所以點(diǎn)擊事件修改如下:

//構(gòu)建一個(gè)新的model并賦值給self.model
let newModel = Model()
newModel.clickTimes = self.model.clickTimes + 1
newModel.show = !self.model.show

self.model = newModel

重新編譯程序后放典,界面可以按照我們的要求顯示。

但是在真實(shí)的開(kāi)發(fā)中船万,這樣寫(xiě)代碼實(shí)在太反人類(lèi)了刻撒,SwiftUI使用ObservableObject解決此問(wèn)題骨田,修改代碼如下:

class Model:ObservableObject {
    @Published var clickTimes = 0
    @Published var show = true
}

struct ContentView: View {
    @ObservedObject var model = Model()
    var body: some View {
        VStack{
            Text(String(self.model.clickTimes)).onTapGesture {
                self.model.clickTimes += 1
                self.model.show.toggle()
            }
            if model.show {
                Text("ContentViewShow")
            }
            SubView(model: model)
        }
    }
}

struct SubView:View {
    @ObservedObject var model:Model

    var body: some View {
        VStack{
            VStack {
                Button(action: {
                    self.model.clickTimes += 1
                    self.model.show.toggle()
                }) {
                    Text(String(self.model.clickTimes))
                }
                if model.show {
                    Text("SubViewShow")
                }
            }
        }
    }
}

首先耿导,model類(lèi)繼承了ObservableObject協(xié)議,同時(shí)SubView和ContentView使用@ObservedObject標(biāo)記了model變量态贤,并且使用@Published標(biāo)記了model的變量舱呻。這些標(biāo)記和協(xié)議底層的實(shí)現(xiàn)方式是Combine,一種類(lèi)似Rx的響應(yīng)式編程方式悠汽。具體的工作流程如下:

  1. 繼承了ObservableObject協(xié)議的類(lèi)箱吕,會(huì)自動(dòng)創(chuàng)建以下變量:
let objectWillChange = PassthroughSubject<Void, Never>()
  1. 使用@Published標(biāo)記的變量發(fā)生改變后,會(huì)使用objectWillChange發(fā)出一個(gè)事件柿冲。

  2. objectWillChange發(fā)出事件后茬高,會(huì)通知使用@ObservedObject的標(biāo)記的畫(huà)面刷新界面。

注意<俪怎栽!@ObservedObject在某些情況下丽猬,會(huì)產(chǎn)生與我們預(yù)料的結(jié)果不一樣的情況!

在如下代碼中熏瞄,ContentView包含一個(gè)Text和一個(gè)SubView脚祟,單擊Text時(shí),會(huì)修改Text的文字强饮,而單擊SubView由桌,通過(guò)model記錄了當(dāng)前點(diǎn)擊Button的次數(shù)。

class Model:ObservableObject {
    @Published var clickTimes = 0
}

struct ContentView: View {
    @State var show:Bool = false
    var body: some View {
        VStack{
            Text(self.show ? "Show" : "hide").onTapGesture {
                self.show.toggle()
            }
            SubView()
        }
    }
}

struct SubView:View {
    @ObservedObject var model = Model()

    var body: some View {
        VStack{
            VStack {
                Button(action: {
                    self.model.clickTimes += 1
                }) {
                    Text(String(self.model.clickTimes))
                }
            }
        }
    }
}

我們?cè)趩螕鬝ubView中的Button時(shí)邮丰,程序似乎按照我們預(yù)想的情況運(yùn)行:

此時(shí)我們點(diǎn)擊了多次Button行您,程序也成功的記錄了次數(shù),然而在我們點(diǎn)擊ContentView中的Text時(shí)候剪廉,出現(xiàn)問(wèn)題了邑雅。

我們的點(diǎn)擊計(jì)數(shù)被清空了!

這是由于我們?cè)邳c(diǎn)擊Text時(shí)候妈经,觸發(fā)了ContentView內(nèi)部的重繪淮野,而且這個(gè)重繪過(guò)程,會(huì)重新生成一個(gè)SubView吹泡,當(dāng)然也會(huì)重新生成SubView中的model骤星,看似合情合理,但是與需求不符合爆哑,為了解決這個(gè)問(wèn)題洞难,在SwiftUI2.0的版本中,推出了@StateObject揭朝,使用此關(guān)鍵字標(biāo)記的model队贱,不會(huì)隨著畫(huà)面重構(gòu)和重新生成,它只會(huì)被創(chuàng)建一次潭袱。這樣就解決了以上的問(wèn)題柱嫌。

3 @EnvironmentObject

@EnvironmentObject和@ObservedObject類(lèi)似,@EnvironmentObject為View的全局屬性屯换,修改上訴例子中所有的@ObservedObject為@EnvironmentObject编丘。

class Model:ObservableObject {
    @Published var clickTimes = 0
    @Published var show = true
}

struct ContentView: View {
    @EnvironmentObject var model:Model
    var body: some View {
        VStack{
            Text(String(self.model.clickTimes)).onTapGesture {
                self.model.clickTimes += 1
                self.model.show.toggle()
            }
            if model.show {
                Text("ContentViewShow")
            }
            SubView()
        }
    }
}

struct SubView:View {
    @EnvironmentObject var model:Model

    var body: some View {
        VStack{
            VStack {
                Button(action: {
                    self.model.clickTimes += 1
                    self.model.show.toggle()
                }) {
                    Text(String(self.model.clickTimes))
                }
                if model.show {
                    Text("SubViewShow")
                }
            }
        }
    }
}

注意,現(xiàn)在創(chuàng)建 SubView()時(shí)彤悔,不需要傳遞model了嘉抓,因?yàn)锧EnvironmentObject為全局屬性,而使用EnvironmentObject時(shí)如下:

ContentView().environmentObject(Model())

此時(shí)晕窑,ContentView中的自建View抑片,都可以通過(guò)@EnvironmentObject標(biāo)記來(lái)獲取model和同步修改。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末杨赤,一起剝皮案震驚了整個(gè)濱河市敞斋,隨后出現(xiàn)的幾起案子级遭,更是在濱河造成了極大的恐慌,老刑警劉巖渺尘,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挫鸽,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡鸥跟,警方通過(guò)查閱死者的電腦和手機(jī)丢郊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)医咨,“玉大人枫匾,你說(shuō)我怎么就攤上這事∧饣矗” “怎么了干茉?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)很泊。 經(jīng)常有香客問(wèn)我角虫,道長(zhǎng),這世上最難降的妖魔是什么委造? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任戳鹅,我火速辦了婚禮,結(jié)果婚禮上昏兆,老公的妹妹穿的比我還像新娘枫虏。我一直安慰自己,他們只是感情好爬虱,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布隶债。 她就那樣靜靜地躺著,像睡著了一般跑筝。 火紅的嫁衣襯著肌膚如雪死讹。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,115評(píng)論 1 296
  • 那天继蜡,我揣著相機(jī)與錄音回俐,去河邊找鬼。 笑死稀并,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的单默。 我是一名探鬼主播碘举,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼搁廓!你這毒婦竟也來(lái)了引颈?” 一聲冷哼從身側(cè)響起耕皮,我...
    開(kāi)封第一講書(shū)人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蝙场,沒(méi)想到半個(gè)月后凌停,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡售滤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年罚拟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片完箩。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赐俗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出弊知,到底是詐尸還是另有隱情阻逮,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布秩彤,位于F島的核電站叔扼,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏漫雷。R本人自食惡果不足惜币励,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望珊拼。 院中可真熱鬧食呻,春花似錦、人聲如沸澎现。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)剑辫。三九已至干旧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間妹蔽,已是汗流浹背椎眯。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留胳岂,地道東北人编整。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像乳丰,于是被迫代替她去往敵國(guó)和親掌测。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容