struct State (英文)
概览
如果视图需要数据来确定各视图间共享的单一数据源,则可以在视图最不常见的上级结构中将数据存储为状态。既可通过一个 Swift 属性以只读方式提供这个数据,也可使用绑定创建与状态的双向连接。SwiftUI 会观察数据的变化,并根据需要更新任何受影响的视图。
请勿将状态属性用于持久存储,因为状态变量的生命周期与视图生命周期是一样的。应将它们用于管理仅影响用户界面的瞬间状态,例如按钮的高亮显示状态、筛选器设置或当前选定的列表项目。你可能还会发现,在你准备对 App 数据模型进行更改之前制作原型时,这种存储很方便。
将可变值作为状态来管理
如果视图需要储存它可以修改的数据,应通过 State
(英文) 属性包装器声明一个变量。例如,你可以在播客播放器视图中创建一个 is
Boolean,以跟踪播客何时运行:
struct PlayerView: View {
@State private var isPlaying: Bool = false
var body: some View {
// ...
}
}
将属性标记为状态会指示框架管理底层存储。你的视图使用属性名称,读取和写入在状态的 wrapped
(英文) 属性中找到的数据。在你更改值时,SwiftUI 会更新视图的受影响部分。例如,你可以向 Player
添加一个按钮,在轻点该按钮后切换存储的值并根据存储的值显示不同的图像:
Button(action: {
self.isPlaying.toggle()
}) {
Image(systemName: isPlaying ? "pause.circle" : "play.circle")
}
通过将状态变量声明为私有变量来限制它们的范围。这确保变量在声明它们的视图层次结构中保持封装状态。
声明 Swift 属性以存储不可变值
若要为视图提供其不修改的数据,请声明一个标准 Swift 属性。例如,你可以扩展播客播放器,以增加一个输入结构,用于包含代表单集标题和节目名称的字符串:
struct PlayerView: View {
let episode: Episode // The queued episode.
@State private var isPlaying: Bool = false
var body: some View {
VStack {
// Display information about the episode.
Text(episode.title)
Text(episode.showTitle)
Button(action: {
self.isPlaying.toggle()
}) {
Image(systemName: isPlaying ? "pause.circle" : "play.circle")
}
}
}
}
尽管单集属性的值对于 Player
是一个常量,但在这个视图的父视图中,它不一定要是常量。当用户在父项中选择另一个单集时,SwiftUI 会检测到状态变化并使用一个新输入来重新创建 Player
。
通过绑定共享状态访问
如果视图需要与一个子视图共享状态的控件,应在带有 Binding
(英文) 属性包装器的子项中声明一个属性。绑定表示对现有存储的引用,从而保留底层数据的单一数据源。例如,如果你将播客播放器视图的按钮重构成一个名为 Play
的子视图,你可以给它提供一个与 is
属性的绑定:
struct PlayButton: View {
@Binding var isPlaying: Bool
var body: some View {
Button(action: {
self.isPlaying.toggle()
}) {
Image(systemName: isPlaying ? "pause.circle" : "play.circle")
}
}
}
如上所示,你可以通过直接引用属性来读取和写入绑定包装的值,这一点与状态属性一样。但是与状态属性不同的是,绑定没有自己的存储,而是引用一个存储在其他地方的状态属性,并提供与该存储的双向连接。
当你实例化 Play
时,可通过添加一个美元符号 ($
) 前缀,提供与父视图中声明的相应状态变量的绑定:
struct PlayerView: View {
var episode: Episode
@State private var isPlaying: Bool = false
var body: some View {
VStack {
Text(episode.title)
Text(episode.showTitle)
PlayButton(isPlaying: $isPlaying) // Pass a binding.
}
}
}
$
前缀要求为它的 projected
(英文) 提供一个包装的属性,这对状态而言,就是与底层存储的绑定。同样,你可以通过使用 $
前缀的绑定获得绑定,从而让你可以在视图层次结构的任意数量层级间传递绑定。
你还可以获得与状态变量中限定范围的值的绑定。例如,如果你在播放器的父视图中将 episode
声明为状态变量,并且单集结构还包含一个你想要通过切换控制的 is
Boolean,那么,你可以引用 $episode
来获得与单集的个人收藏状态的绑定:
struct Podcaster: View {
@State private var episode = Episode(title: "Some Episode",
showTitle: "Great Show",
isFavorite: false)
var body: some View {
VStack {
Toggle("Favorite", isOn: $episode.isFavorite) // Bind to the Boolean.
PlayerView(episode: episode)
}
}
}
为状态过渡添加动画效果
当视图状态发生改变时,SwiftUI 会立即更新受影响的视图。如果你需要实现顺畅的视觉过渡,可以将触发过渡的状态更改包装在对 with
(英文) 函数的调用中,以指示 SwiftUI 为它们添加动画效果。例如,你可以为由 is
Boolean 控制的更改添加动画效果:
withAnimation(.easeInOut(duration: 1)) {
self.isPlaying.toggle()
}
通过更改动画函数结尾闭包中的 is
,可指示 SwiftUI 为依赖于包装值的一切内容添加动画效果,例如,按钮图像上的缩放特效:
Image(systemName: isPlaying ? "pause.circle" : "play.circle")
.scaleEffect(isPlaying ? 1 : 1.5)
SwiftUI 会使用你指定的曲线和持续时间,如果你未提供则使用合理的默认值,在一段时间内在给定的 1
和 1
值之间过渡缩放特效输入。另一方面,图像内容不会受到动画的影响,即使同一个 Boolean 规定要显示哪个系统图像也是如此。那是因为 SwiftUI 无法以有意义的方式在两个字符串 pause
和 play
之间逐步过渡。
你可以向状态属性添加动画,或与上述示例一样,向绑定添加动画。无论是哪一种方式,在底层存储的值发生变化时,SwiftUI 都会为发生的任何视图变化添加动画效果。例如,如果你在动画块位置上方的某个视图层次结构层级向 Player
添加背景色,SwiftUI 同样会为此添加动画效果:
VStack {
Text(episode.title)
Text(episode.showTitle)
PlayButton(isPlaying: $isPlaying)
}
.background(isPlaying ? Color.green : Color.red) // Transitions with animation.
如果你想将动画应用于特定的视图,而不是状态变化触发的所有视图,请改用 animation(_:)
(英文) 视图修饰符。