SwiftUI 借鉴了 React 等 UI 框架的概念,经过 state 的变化,对 View 进行响应式的渲染。主要经过 @State, @StateObject, @ObservedObject 和 @EnvironmentObject 等属性包装器 (property wrapper) 将属性包装成状态来实现。html
@State 和 @StateObject 是比较经常使用的属性包装器。git
二者的区别是:swift
举例说明。在一个 SwiftUI View 中声明属性:app
@State var name: String
那么,每次 name 发生变化时,View 都会从新渲染。框架
但假若有一个类:ui
class Student { var name: String = "" }
当它的实例被用 @State 修饰时:3d
@State var student: Student = Student()
则 View 不会随着 student.name
的变化而变化,由于实例 student 自己并无发生变化。为了让 View 随 student 的属性变化,就要用到 @StateObject 来包装:code
@StateObject var student: Student = Student()
同时还要给 Student 的属性添加 @Published 包装:视频
class Student: ObservableObject { @Published var name: String = Student() }
如此,每次 student.name
发生变化时,View 就会随之从新渲染了。htm
有时候咱们在子 View 中须要用到父 View 的属性,而且不单单单方面的显示,还要有双向的影响,即子 View 对属性的更改,能反应到父 View 上。
在如下状况下:
// Parent View struct ForumView: View { @State var username: String = "" var body: some View { Text(username) InputView(name: username) } } // Child View struct InputView: View { @State var name: String = "" var body: some View { Text(name) Button(action: { name = "Tom" }) { Text("Change Name") } } } // Preview: ForumView(username: "Jack")
父 View 虽然能把本身属性的值传给子 View,可是子 View 在改变其属性值时,仅可以改变它自身,而不能影响到父 View。若要影响到父 View,就须要用到 @Binding 了:
// Parent View struct ForumView: View { @State var username: String = "" var body: some View { Text(username) InputView(name: $username) } } // Child View struct InputView: View { @Binding var name: String var body: some View { Text(name) Button(action: { name = "Tom" }) { Text("Change Name") } } } // Preview: ForumView(username: "Jack")
此时,你经过子 View 改变的值 (name),就同时也能改变到父 View 的属性 (username) 了。
简而言之,@Binding 就是对其余属性的一种引用式的绑定。注意用法:它在声明时,不须要赋初始值,在用到时,要加前缀 $
。
@Binding 对应 @State,则 @ObjectBinding 便对应 @ObservedObject 和 @StateObject 了,毋庸赘言。
用 @ObservedObject 和 @StateObject 包装的属性都须要其对象类实现 ObservableObject 协议。本质上,他们都是用来让对象状态化的包装器。但在使用时,有必定区别。
简单地说,@ObservedObject 会在 View 每次被从新渲染时从新构造,它包装的 Model 是跟着 View 走的,而 @StateObject 则不会,它一旦被建立,就由 SwiftUI 接管,不会随着 View 的刷新渲染而重建。
为何会这样,由于 View 做为 struct 是一个值类型的对象,他被销毁时,它内部的对象也会被销毁,而 @StateObject 等因而给 View 内部的对象加了一层保护,使其不受 View 生命周期的影响。
有时咱们经过 NavigationView 来回切换页面,会发现 @StageObject 对象也被重置了,像是随着 View 刷新而重建同样,其实那是 SwiftUI 的行为。
比较而言,我以为 @StateObject 更好,由于它和 View 解耦了,更方便控制。
@EnvironmentObject 有 @StateObject 那种脱离 View 生命周期的特性,但在使用上更为灵活。举例来讲:
@StateObject var thing: Thing
,包含 View B通常来讲,为了让 View C 用到 View A 的 thing,就须要从 View A 开始传递 thing 给 View B, 再由 View B 传给 View C 使用。这是否是太麻烦了,View B 凭空多了一个它用不到但却能访问的对象 thing。@EnvironmentObject 的存在就是为了解决这个问题。
在 View A 中:
var thing: Thing = Thing(tag: "e") var body: some View { NavigationView { ViewB() }.environmentObject(thing) }
经过 .environmentObject()
, thing 变成了环境对象。接下来咱们在 View C 中就能够直接使用了:
@EnvironmentObject var thing: Thing var body: some View { // thing.tag: "e" Text(thing.tag) }
能够看到 View C 中 @EnvironmentObject var thing: Thing
不用初始化 thing,由于这个 thing 就是 ViewA 中的 thing。EnvironmentObject 就像把一个对象全局化了同样。
本文主要参考了如下文章和视频: