在 SwiftUI 中,视图的状态管理分为两种:一种是只读的数据,称之为 Swift 的属性(Property);一种则是可读可写的,需要使用 State
进行数据绑定。子视图的状态管理都存在最近的公共祖先视图上,如下图:
当使用 State
进行绑定之后,当数据源发生改变的时候,SwiftUI 会自动去更新需要改变的视图。
使用 Swift 的属性来存储不变的数据
使用属性来存储一些不变的数据,比如视频的标题:
struct ContentView: View {
let videoTitle = "视频标题"
var body: some View {
Text(videoTitle)
}
}
首先,声明一个 videoTitle 属性来存储视频标题,然后在 Text 中展示标题内容。
State 的使用
当视图的某个数据需要改变时,使用 State
的属性包裹器声明即可。比如播放按钮,需要根据播放的状态来改变按钮的图标。我们可以声明一个 isPlaying
的属性来标明是否正在播放:
struct ContentView: View {
@State private var isPlaying = false
var body: some View {
Button(action: {
self.isPlaying.toggle()
}) {
Image(systemName: isPlaying ? "pause.circle" : "play.circle")
}
}
}
首先,我们声明一个用 @State
修饰的布尔类型的属性 isPlaying。接着,在 body 中返回一个 Button。Button 的 action 则是调用一个 toggle()
函数。它的作用是将 true 变为 false,或者将 false 变为 true。最后,则是根据 isPlaying 的值来决定按钮的图标是播放还是暂定。
当我们将 isPlaying 用 State 包裹时,就相当于告诉 SwiftUI 它需要管理底层存储的数据。 Button 通过 state 的 wrappedValue
属性去读写存储的数据。当你修改了 isPlaying 的值,SwiftUI 则会去更新 Image 视图。
关于 wrappedValue:通过底层代码可以看到,State 是一个结构体。而 wrappedValue 则是它的一个属性,从名字也可以看出是用来存储底层的数据。通常,开发者不需要显式的去调用它,直接访问 State 包裹的属性即可正常的访问底层存储的数据。
需要注意的是,声明 State 属性的时候,应该加上 private
修饰符,这样可以保证属性的生命周期与相应的视图生命周期保持一致。所以,对于持久化存储的数据不要使用 State,因为这样会导致数据的生命周期长过视图的生命周期。State 应该用于保存视图的用户交互相关的数据,比如按钮的高亮状态,过滤设置等。
通过 Bind 共享数据
如果我们想让子视图也可以访问 State 存储的数据,可以通过 @Bind
来实现。比如下面的代码:
// 播放按钮
struct PlayButton: View {
@Binding var isPlaying: Bool
var body: some View {
Button(action: {
self.isPlaying.toggle()
}) {
Image(systemName: isPlaying ? "pause.circle" : "play.circle")
}
}
}
// 播放视图
struct PlayerView: View {
@State private var isPlaying: Bool = false
var body: some View {
PlayButton(isPlaying: $isPlaying)
}
}
PlayButton_Previews 如果默认代码会报错,在里面声明一个 State 属性即可:
struct PlayButton_Previews: PreviewProvider {
@State static var value = false
static var previews: some View {
PlayButton(isPlaying: $value)
}
}
$ 前缀会去读取 projectedValue
的值,它是 Bind 底层存储数据的属性。