【WWDC2019 之 SwiftUI】03 - SwiftUI 的數據流

這篇文章我們來看一下在 SwiftUI 中如何將數據作為依賴連接起來,同時保持 UI 的顯示是正確并可預測的卦停。這里主要講解 SwiftUI 中的五個數據流工具:Property助泽、@State奔滑、@Binding妖谴、@ObjectBinding@EnvironmentObject簿煌。

數據流工具

Property

Property 是我們目前開發(fā)中最常見的磁携,它就是一個簡單的屬性褒侧,沒什么特別。例子:

struct ContentView : View {
    var body: some View {
        ChildView(text: "Demo")
    }
}

struct ChildView: View {
    let text: String
    
    var body: some View {
        Text(text)
    }
}

ChildView 需要 Parent View 給它傳一個字符串,并且 ChildView 本省不需要對這個字符串進行修改闷供,所以直接定義一個 Property烟央,在使用的時候,直接讓 Parent View 告訴它就好了歪脏。

@State

我們先看一個官方給的錯誤例子:

struct PlayerView : View {
    let episode: Episode
    var isPlaying: Bool
    
    var body: some View {
        VStack {
            Text(episode.title)
            Text(episode.showTitle).font(.caption).foregroundColor(.gray)
            
            Button(action: {
                // 錯誤:Cannot use mutating member on immutable value: 'self' is immutable
                self.isPlaying.toggle()
            }) {
                Image(systemName: isPlaying ? "pause.circle" : "play.circle")
            }
        }
    }
}

上面的代碼中疑俭,我們想在 Button 被點擊后直接使用 self.isPlaying.toggle() 切換 isPlaying 的值,但這是不行的婿失,因為 PlayerView 是 struct 類型钞艇,self 是不可變的,并且 isPlaying 是一個普通的屬性豪硅。為了達到我們的需求哩照,@State 的作用就來了。我們把上面的代碼改成:

struct PlayerView : View {
    let episode: Episode
    @State private var isPlaying: Bool = false
    
    var body: some View {
        VStack {
            Text(episode.title)
            Text(episode.showTitle).font(.caption).foregroundColor(.gray)
            
            Button(action: {
                self.isPlaying.toggle()
            }) {
                Image(systemName: isPlaying ? "pause.circle" : "play.circle")
            }
        }
    }
}

我們用 @State 標記 isPlaying 屬性舟误,這樣 isPlaying 就可以在 View 的內部被更改葡秒,并且被更改后,與 isPlaying 相關的 View 也會更新嵌溢,本例中 Image 就會在 pause.circleplay.circle 之間切換。

總結:@State 的作用是讓被它標記的屬性可以在 View 內部修改蹋岩,并且 View 也會重新渲染赖草。

@Binding

有時候我們想讓 Child View 修改 Parent View 傳給它的數據,并且數據修改后剪个,Parent View 重新渲染秧骑。這時我們就得用到 @Binding

我們把 @State 例子中的 Button 重構為 PlayButton扣囊,代碼如下:

struct PlayerView : View {
    let episode: Episode
    @State private var isPlaying: Bool = false
    
    var body: some View {
        VStack {
            Text(episode.title)
            Text(episode.showTitle).font(.caption).foregroundColor(.gray)
            PlayButton(isPlaying: $isPlaying)
        }
    }
}

struct PlayButton : View {
    @Binding var isPlaying: Bool
    
    var body: some View {
        Button(action: {
            self.isPlaying.toggle()
        }) {
            Image(systemName: isPlaying ? "pause.circle" : "play.circle")
        }
    }
}

PlayButton 中乎折,用 @Binding 標記 isPlaying 屬性,意味著可以對傳入的數據進行修改侵歇;在 PlayerView 使用時骂澄,傳入的屬性 isPlaying 需要有 $ 前綴,并且被傳入屬性不能是普通的屬性惕虑,而要求是可讀可寫的屬性(被@State / @Binding / @ObjectBinding 標記)坟冲。

@Binding 在很多系統(tǒng)自帶的 View 中使用,如 Toggle溃蔫、TextFieldSlider 等等健提。

@ObjectBinding

其實在很多情況下,我們的數據來源于外部的數據模型伟叛。我們也想要在當外部數據發(fā)生變化時私痹,能及時更新我們的 UI。而 @ObjectBinding 就是為這種需求而設計的。

對于 @ObjectBinding標記的屬性紊遵,它必須遵循 BindableObject 協(xié)議账千,這個協(xié)議的定義如下:

public protocol BindableObject : AnyObject, DynamicViewProperty, Identifiable, _BindableObjectViewProperty {
    associatedtype PublisherType : Publisher where Self.PublisherType.Failure == Never
    var didChange: Self.PublisherType { get }
}

Publisher 是與 SwiftUI 一起推出的響應式編程框架 Combine 的一個協(xié)議。所以想要熟練使用 BindableObject癞蚕, 學習 Combine 是必不可少的蕊爵。

下面是 @ObjectBinding 的演示代碼:

class MyModelObject : BindableObject {
    var didChange = PassthroughSubject<Void, Never>()
    
    func changeData() {
        // 修改數據
        // ...
        
        // 通知訂閱者數據發(fā)生變化
        didChange.send()
    }
}

struct MyView : View {
    @ObjectBinding var model: MyModelObject
    
    // ...
}

當調用 didChange.send() 之后,MyView 接收到通知桦山,View 就會重新渲染攒射。

@ EnvironmentObject

我們剛剛學習的 Property@Binding 都只能從 Parent View 一層一層的往 Child View 傳遞。所以當我們的 View 層級關系比較復雜恒水、有些屬性只在很深層級的 View 才用到時会放,用 Property@Binding 的方式就會非常麻煩。蘋果使用 @ EnvironmentObject 來解決這個問題钉凌。

我們先看一個 demo咧最,然后通過 demo 來講解 @ EnvironmentObject 的使用。

class MyModelObject : BindableObject {
    var didChange = PassthroughSubject<Void, Never>()
    var count = 0
    
    func updateCount() {
        count += 1
        didChange.send()
    }
}

struct ContentView : View {
    var body: some View {
        RootView().environmentObject(MyModelObject())
    }
}

struct RootView: View {
    var body: some View {
        VStack(spacing: 20) {
            ChildView1()
            ChildView2()
        }
    }
}

struct ChildView1: View {
    @EnvironmentObject var model: MyModelObject

    var body: some View {
        Button(action: {
            self.model.updateCount()
        }) {
            Text("Button")
        }
    }
}

struct ChildView2: View {
    @EnvironmentObject var model: MyModelObject
    
    var body: some View {
        Text("\(model.count)")
    }
}

RootView 包含了 ChildView1ChildView2御雕,兩個 Child View 都持有被 @EnvironmentObject 標記的 MyModelObject 類型的屬性矢沿,當 ChildView1 的按鈕被點擊時,MyModelObject 的數據被更新酸纲,ChildView2 的 View 重新渲染捣鲸。整個過程中兩個 Child View 沒有從 RootView 中直接接受參數,只有 RootView在初始化的時闽坡,通過 environmentObject() 方法把 MyModelObject 注入到整個 View 層級中栽惶,這個層級中所有的 View 都可以通過 @Environment的方式訪問 MyModelObject 。需要注意的一點是疾嗅,使用 environmentObject() 注入的對象必須是 BindableObject 類型外厂。

五個數據流工具總結

  • Property:當 View 所需要的屬性只要求可讀,則使用 Property代承。
  • @State: 當 View 所需要的屬性只在當前 View 和它的 Child Views 中使用汁蝶,并且在用戶的操作過程中會發(fā)生變化,然后導致 View 需要作出改變次泽,那么使用 @State穿仪。 因為只在當前 View 和它的 Child Views 中使用,跟外界無關意荤,所以被 @State 標記的屬性一般在定義時就有初始值啊片。
  • @Binding:當 View 所需要的屬性是從它的直接 Parent View 傳入,在內部會對這個屬性進行修改玖像,并且修改后的值需要反饋給直接 Parent View紫谷,那么使用 @Binding齐饮。
  • @ObjectBinding:用于直接綁定外部的數據模型和 View。
  • @EnvironmentObject:Root View 通過 environmentObject()BindableObject 注入到 View 層級中笤昨,其中的所有 Child Views 可以通過 @EnvironmentObject 來訪問被注入的 BindableObject祖驱。

接收其他外部變化

有時我們的 View 需要監(jiān)聽外部的其他變化,并做出相應的改變瞒窒,可以使用 receive(on:)捺僻,這里面的 closure 參數是在主線程執(zhí)行的。

以下是官方的 Demo 代碼:

struct PlayerView : View {
    let episode: Episode
    @State private var isPlaying: Bool = true
    @State private var currentTime: TimeInterval = 0.0
    
    var body: some View {
        VStack {
            // ...
            Text("\(playhead, formatter: currentTimeFormatter)")
        }
        .onReceive(PodcastPlayer.currentTimePublisher) { newCurrentTime in
            self.currentTime = newCurrentTime
        }
    }
}

總結

數據在整個 App 中是非常重要的一部分崇裁,在使用上面講到的工具之前匕坯,先仔細研究自己的數據結構,然后選擇合適的工具拔稳,把數據注入到 UI 中葛峻。

想要更詳細了解文章的內容,可以點擊查看下面的視頻巴比。想及時看到我的新文章的术奖,可以關注我。

參考資料

Data Flow Through SwiftUI - WWDC 2019 - Videos - Apple Developer

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末轻绞,一起剝皮案震驚了整個濱河市采记,隨后出現的幾起案子涣易,更是在濱河造成了極大的恐慌寞奸,老刑警劉巖全景,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件篡九,死亡現場離奇詭異,居然都是意外死亡扒寄,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來然走,“玉大人,你說我怎么就攤上這事戏挡∩秩穑” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵褐墅,是天一觀的道長拆檬。 經常有香客問我,道長妥凳,這世上最難降的妖魔是什么竟贯? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮逝钥,結果婚禮上屑那,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好持际,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布沃琅。 她就那樣靜靜地躺著,像睡著了一般蜘欲。 火紅的嫁衣襯著肌膚如雪益眉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天姥份,我揣著相機與錄音郭脂,去河邊找鬼。 笑死殿衰,一個胖子當著我的面吹牛朱庆,可吹牛的內容都是我干的。 我是一名探鬼主播闷祥,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼娱颊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了凯砍?” 一聲冷哼從身側響起箱硕,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎悟衩,沒想到半個月后剧罩,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡座泳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年惠昔,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挑势。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡镇防,死狀恐怖,靈堂內的尸體忽然破棺而出潮饱,到底是詐尸還是另有隱情来氧,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布香拉,位于F島的核電站啦扬,受9級特大地震影響,放射性物質發(fā)生泄漏凫碌。R本人自食惡果不足惜扑毡,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望证鸥。 院中可真熱鬧僚楞,春花似錦勤晚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至膜赃,卻和暖如春挺邀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背跳座。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工端铛, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人疲眷。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓禾蚕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親狂丝。 傳聞我的和親對象是個殘疾皇子换淆,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內容