SwiftUI #25: 状态 (@State)
Source: Dev.to
@State
@State 是一个 property‑wrapper,它将 SomeType 类型的值包装在 State 结构体中,并在该值变化时通知系统,从而使视图自动更新。
注意: 标记为
@State的属性应当声明为private,因为它代表视图的内部状态,不应被外部使用者修改。
struct ContentView: View {
@State private var title: String = "Título inicial"
var body: some View {
VStack {
Text(title)
Button {
title = "Otro título"
} label: {
Text("Modificar")
}
}
}
}
在上面的示例中,当 title 变化时,@State 会将变化通知系统,body 的内容会重新渲染。
Source: …
Binding
Binding 是对由 @State 存储的值的引用。
为了让视图能够内部修改由 @State 存储的值,需要通过其 投影值(projected value)传递对 State 的引用,投影值可以通过在变量名前加 $ 前缀获得。
struct ContentView: View {
@State private var tituloFinal: String = "Título inicial"
@State private var title: String = ""
var body: some View {
VStack {
Text(tituloFinal)
.padding(10)
TextField("Ingrese título", text: $title)
Button {
tituloFinal = title
} label: {
Text("Actualizar título")
}
}
}
}

接收 Binding 的子视图
当一个视图需要修改外部 @State 包装的值时,需要声明一个类型为 Binding 的实例属性,并使用 property‑wrapper @Binding。
struct ContentSubview: View {
var title: String
@Binding var titleInput: String
var body: some View {
VStack {
Text(title)
.padding(10)
TextField("Ingrese título", text: $titleInput)
}
}
}
在主视图中使用子视图
struct ContentView: View {
@State private var tituloFinal: String = "Título inicial"
@State private var title: String = ""
var body: some View {
VStack {
ContentSubview(title: tituloFinal, titleInput: $title)
Button {
tituloFinal = title
} label: {
Text("Actualizar título")
}
}
}
}
重要:
@Binding属性始终从@State属性获取值,因此不需要为其提供默认值。
State 和 Binding 作为结构体
Swift 允许通过在变量名前加下划线 (_) 前缀来访问 property‑wrapper 的底层结构。
表格属性
| 属性 | 描述 |
|---|---|
wrappedValue | 由 @State 管理的值。可直接通过属性名访问(不使用 _)。 |
projectedValue | Binding,作为对 State 的引用。通过 $ 访问(相当于 _名称.projectedValue)。 |
struct ContentSubview: View {
var title: String
@Binding var titleInput: String
var body: some View {
VStack {
Text(title)
.padding(10)
TextField("Ingrese título", text: _titleInput.projectedValue)
.textFieldStyle(.roundedBorder)
}
}
}
struct ContentView: View {
@State private var tituloFinal: String = "Título inicial"
@State private var title: String = ""
var body: some View {
VStack {
// Acceso a la estructura subyacente
ContentSubview(
title: _tituloFinal.wrappedValue,
titleInput: _title.projectedValue
)
Button {
_tituloFinal.wrappedValue = _title.wrappedValue
} label: {
Text("Actualizar título")
}
}
}
}
Binding 手册
虽然通常将 Binding 与 @State 一起使用,但也可以通过初始化器 init(get:set:) 手动创建 Binding,该初始化器接受两个 闭包:一个用于获取值,另一个用于设置值。这在例如预览期望接收 Binding 的组件时非常有用。
struct ContentSubview: View {
var title: String
@Binding var titleInput: String
var body: some View {
VStack {
Text(title)
.padding(10)
TextField("Ingrese título", text: _titleInput.projectedValue)
.textFieldStyle(.roundedBorder)
}
}
}
使用手动 Binding 的预览
#Preview("Manual binding") {
var someTitle = ""
// Binding manual que opera sobre `someTitle`
let titleBinding = Binding(
get: { someTitle },
set: { newValue in
someTitle = newValue
}
)
ContentSubview(title: "Vista previa", titleInput: titleBinding)
}
在视图中使用 Binding
struct ContentSubview: View {
let title: String
@Binding var titleInput: String
var body: some View {
VStack {
Text(title)
TextField("Título", text: $titleInput)
}
}
}
手动创建 Binding
struct ContentView: View {
@State private var title = ""
var body: some View {
ContentSubview(title: "Título inicial", titleInput: $title)
}
}
#Preview("Manual binding") {
@State var title = ""
ContentSubview(title: "Título inicial", titleInput: $title)
}
注意: 在预览 (
#Preview) 中不显示全屏按钮,因此 Enter fullscreen mode 和 Exit fullscreen mode 控件被省略。
使用常量 Binding
有时将不可变值传递给 Binding 会更方便。在这种情况下使用静态方法 .constant(_:):
#Preview("Binding constante") {
ContentSubview(title: "Título inicial", titleInput: .constant("Algún título"))
}
从 #Preview 创建 State
从 iOS 17/macOS 14 开始,宏 @Previewable 允许直接在 #Preview 块中声明 @State,简化了测试视图的编写。
#Preview("@Previewable") {
@Previewable @State var title = ""
ContentSubview(title: "Título inicial", titleInput: $title)
}
提示:
@Previewable简化了测试视图中的状态管理,因为在编辑代码时状态会实时更新。