Demo
@State
由 SwiftUI 管理的可读写的属性包装器,当修饰的属性值改变的时候,界面会随之更新。由 @State 包装的属性通常用 private 修饰,在 body 内使用。
下面的实例是一个可以切换天气的界面,并且可以控制天气是否可变。
1 | enum Weather: String, CaseIterable { |
@Binding
接下来,我们用自定义的 WeatherView 去展示天气图片,图片会跟随父视图天气变化做相应改变,并且在 WeatherView 中可以通过点击改变天气。这个时候,我们就需要用到 @binding 来做数据的双向绑定。
1 | struct WeatherView: View { |
接下来我们在 // add weather view
下面添加如下代码:
1 | WeatherView(weather: $weather, mutableWeather: $mutableWeather) |
@ObservedObject、@Published、ObservableObject
遵循 ObservableObject 协议的类的属性可以用 @Published 包装,在多个界面之间同步数据,只需要将需要监听的实例对象用 @ObservedObject 包装即可。注意:这里适用的对象类型是 class,因为 class 是在内存中共享数据的。
下面是一个可以编辑一个人姓名和年龄的实例,在 EditView 所做的更改,会同步至 Person 界面。
1 | class Person: ObservableObject { |
@Environment、EnvironmentValues
@Environment 可以让我们在 View 中直接访问预设的环境变量,比如系统是否暗黑模式、系统日历、时区等。
下面是一个可以返回上一个页面的实例,@Environment
将.presentationMode
绑定在当前的 View,我们可以直接调用presentationMode
来获取它的值:
1 | struct EnvironmentView: View { |
系统为我们提供了许多有用的预设变量,详见 https://developer.apple.com/documentation/swiftui/environmentvalues
我们也可以为预设值注入新值,比如我们将返回按钮标题改为 “Dismiss\nDismiss”,这时可以看见一个换行的按钮,而当我们给.lineLimit
注入新值得时候,按钮标题就只有一行了:
1 | Button("Dismiss\nDismiss") { |
这里只是举个例子,我们使用.lineLimit(1)
也能达到同样的效果。
我们也可以自定义 EnvironmentValues:
1 | struct DismissColorKey: EnvironmentKey { |
然后我们添加一个新的属性:
1 | @Environment(\.dismissColor) private var dismissColor |
再使用自定义的预设值添加一个红色的返回按钮:
1 | Button(action: { |
@EnvironmentObject
@EnvironmentObject 和 @ObservedObject 很像,都需要遵循 ObservableObject 协议,都可以同步数据状态,但是它具备更强大的功能,那就是子视图可以自动获取父视图注入的环境变量。
比如我们有如下视图层级:A -> B -> C -> D -> E,后一个是前一个视图的子视图。如果我们使用 @ObservedObject 在 A 视图包装一个变量,我们需要在每个视图包装一个变量,将变量一层层传递到 E 视图。而使用 @EnvironmentObject 我们不需要这么复杂,我们在 A 视图声明一个变量后,在 E 视图用 @EnvironmentObject 包装一个变量后,就可以获取到 A 视图注入的环境变量了,而且可以同步数据的修改,这简直是太方便了。要注意的是,如果 E 视图找不到这个环境变量,程序会崩溃,所以要确保 E 视图能获取到注入的环境变量。
1 | class User: ObservableObject { |
Demo