SwiftUI #25: 状态 (@State)

发布: (2026年1月10日 GMT+8 04:18)
5 min read
原文: Dev.to

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")
            }
        }
    }
}

SwiftUI 中 Binding 示例

接收 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 属性获取值,因此不需要为其提供默认值。

StateBinding 作为结构体

Swift 允许通过在变量名前加下划线 (_) 前缀来访问 property‑wrapper 的底层结构。

表格属性

属性描述
wrappedValue@State 管理的值。可直接通过属性名访问(不使用 _)。
projectedValueBinding,作为对 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 modeExit 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 简化了测试视图中的状态管理,因为在编辑代码时状态会实时更新。

Back to Blog

相关文章

阅读更多 »

SwiftUI #21:组

什么是 Group?Group 将多个视图聚合在一起,以避免 Stack 的子视图限制(最大 10),并允许对多个视图应用样式。

SwiftUI #20: 优先级

介绍 在 SwiftUI 中,Stack 会在其视图之间等距划分空间。如果视图放不下,它会为 Image 分配固定大小并进行缩减……