Data Flow Through SwiftUI - SwiftUI 中的数据流
摘要
如何让数据的变化反映到最终的用户界面。当用户或外部的事件改变了数据,如何操作这些数据流?
Principles of Data Flow - 数据流的原则
数据是所有驱动 UI 的信息。SwiftUI 为数据流提供了一系列工具:Property,@Environment,BindableObject,@State,@Binding。

开发者过去需要小心的把变化的数据同步到各种视图和他们的子视图,这通常会使得代码变得复杂和易出 bug。SwiftUI 引入了 Single Source of truth,即单一信息源的原则。位于父视图中的属性,可以被子视图访问。

@State - 状态
@State 就是 Source of truth。
在 View 的 struct 里声明属性时,使用 @State 关键字表示视图中的 UI 元素会依赖该属性的值动态改变。当属性值变化时,SwiftUI 会使用新的值重新从 body 生成一个新的 View,达到数据改变映射到用户界面的效果。
@State 是一个“Property Wrapper”,即属性包装。当一个属性使用 @State 关键词时,Swift 会为这个属性添加一些额外的操作,比如 Bool 类型可以获得 toggle() 方法,用于在 true/false 之间切换。
视图经常会被系统重建,所以需要给属性赋一个默认的值。若属性被标记为 @State,系统会使用储存的变量的值,而不是每次都使用初始化的值。
同时,为了保证运行效率,系统会比较值改变导致哪些视图需要被重新渲染,最后只重新渲染那些需要更新的视图。

数据流向是单向的:用户产生一个交互事件,导致视图中某个 @State 的属性发生变化,然后 SwiftUI 更新对应的 View,最后把渲染后的界面反馈给用户。

@Binding - 绑定
@Binding 为子视图提供读/写 Source of truth 的方法
如果要将子视图打包成一个新的自定义视图,同时又依赖了父视图的 @State 的属性,那么这种情况不应该在子视图里使用 @State 来标记属性。因为使用 @State 会导致子视图中的属性成为新的 Source of truth。
子视图的渲染依赖父视图的属性,所以它不应该有自己的 Source of truth。它只需要获取父视图的这个属性,然后修改它。
这种情况,应该要在子视图中使用 @Binding。

@Binding 标记的属性不需要一个初始化的值,因为这个值会从父视图的 @State 属性自动生成。

只需要在父视图中,声明子视图的时候传入带美元符号的属性名即可完成绑定。父视图依旧持有 @State 这个 Source of truth,同时子视图可以通过绑定访问父视图的属性。
通过这种优雅的数据流控制方式,我们不再需要 ViewController 了!
某些事件是从外部发生的,例如:计时器,通知。

在视图中添加类似于 .onRecive() 的 Modifier 可以监听外部事件。外部数据会传入到闭包中,可以直接在闭包中通过 “self.属性 = 新的值” 来修改视图的 Source of truth 数据,达到修改视图的效果。

可以通过在定义 class 的时候实现 BindableObject 协议,使得对象能与 SwiftUI 中的视图绑定。当 SwiftUI 中的 model 发生改变时,也会将改变反映在视图中。


@EnvironmentObject
@EnvironmentObject 是一个比较全局的对象。它可以跨视图让数据在所有视图中正确地被渲染。使用 @EnvironmentObject 可以避免 @ObjectBinding 需要视图层级之间传递数据的麻烦。

