@State 是 SwiftUI 中的 Property Wrapper,用于管理单个视图内的局部状态。SwiftUI 会在每次 @State 属性变化时自动重绘视图,使界面无需手动调用更新即可实现响应式。Apple Developer Documentation (2025) 指出,@State 推荐用于属于单个视图的简单类型和结构。@State 是为 SwiftUI 界面添加交互性的最简单方式。
要点
@State 是 SwiftUI 中内置的 Property Wrapper,允许视图存储和跟踪自己的状态。当 @State 值发生变化时,SwiftUI 会自动重绘视图,重新调用 body 属性。这是 SwiftUI 中响应式编程的基础:开发者声明状态,框架负责界面的同步。
@State 在堆上创建一个由 SwiftUI 管理的存储区域。这个区域是持久化的——它能在每次渲染时发生的视图结构重复初始化中存活下来。SwiftUI 使用视图标识符(基于在层次结构中的位置生成)将 @State 属性绑定到特定视图。因此,当父视图更新时,状态不会被重置。
重要限制:@State 仅适用于值类型(结构体、枚举、基本类型)。对于引用类型(类),请使用 @StateObject 或 @ObservedObject。如果将类分配给 @State 属性,SwiftUI 将无法检测到对象内部的变化——只能检测整个引用的替换。
SwiftUI 通过内部存储机制实现 @State。每个 @State 属性获得一个专用的内存单元,存储在视图的特殊存储容器中。当对 wrappedValue 进行写入时,SwiftUI 通过 didSet 通知其依赖图需要重绘。
struct ContentView: View {
@State private var name: String = "User"
@State private var isLoggedIn: Bool = false
var body: some View {
VStack {
Text("你好,\(name)")
Button(isLoggedIn ? "退出登录" : "登录") {
isLoggedIn.toggle()
}
}
}
}
在示例中有两个 @State 属性:name(String)和 isLoggedIn(Bool)。调用 isLoggedIn.toggle() 时,SwiftUI 将 ContentView 标记为需要更新,并在下一个渲染周期中重新执行 body。关键点:@State 属性始终使用 private 修饰符声明——这表示状态仅属于当前视图,不应从外部直接修改。
为了观察变化,SwiftUI 使用 Combine 中的 CurrentValueSubject。每个 @State 属性创建一个隐藏的发布者,在每次变化时通知系统。这使得 SwiftUI 只需重绘最小必要视图集,避免完全更新层次结构。
@State 最适合简单的局部状态:搜索文本框、模态窗口的布尔标志、设置开关、计数器、选中的列表项。如果值仅在一个视图及其嵌套组件(通过 @Binding)中使用,@State 是正确的选择。对于需要在视图关闭后仍然保留的状态(如表单数据),只要视图保持在层次结构中,@State 也是合适的。
不要将 @State 用于应用程序的全局状态、网络数据缓存或在多个屏幕中使用的对象。这些用途应由 @StateObject 和 @EnvironmentObject 处理。@State 也不适合存储大量数据——每次变化时整个视图都会被重绘。
@Binding 是父视图中 @State 与需要修改此状态的子视图之间的桥梁。父视图声明 @State,子组件通过 $ 投影获取 Binding。子视图中 Binding 的更改会自动更新父视图中的 @State——反之亦然。这确保了具有反馈可能的单向数据流。
struct ParentView: View {
@State private var text: String = ""
var body: some View {
ChildView(text: $text)
}
}
struct ChildView: View {
@Binding var text: String
var body: some View {
TextField("Enter text", text: $text)
}
}
在代码中,ParentView 拥有 @State text,ChildView 接收 $text 作为 Binding。ChildView 内的 TextField 通过 text: $text 绑定到此 Binding。当用户在 TextField 中键入时,值通过 Binding 在 ChildView 中发生变化,从而触发 ParentView 中 @State 的更新。两个视图都使用新值重绘。
最常见的错误——将类分配给 @State 属性。如果您写 @State var model = MyClass(),SwiftUI 将无法跟踪类内部属性的变化——只能跟踪对象本身的替换。对于类始终使用 @StateObject。第二个常见问题——在声明 @State 时不使用 private 修饰符,这违反了状态封装原则。
直接将 @State 传递给子视图而不使用 $——另一个典型错误。如果您传递 TextField(text: text) 而不是 TextField(text: $text),子组件将收到普通字符串而非 Binding。TextField 中文本的更改不会与父 @State 同步。始终使用 $ 投影来传递 Binding。
第三个错误——对相关数据使用多个 @State 属性。如果多个值逻辑上构成一个整体(如表单字段),将它们合并为一个结构体和一个 @State。这简化了向子视图传递状态,并减少了单独的更新触发器数量。
@State 在大多数 SwiftUI 项目中用于基本交互。让我们看一个登录表单的示例,其中 @State 管理文本字段和加载状态。这种模式在每个应用程序中都能找到——从简单的笔记到复杂的企业解决方案。
struct LoginView: View {
@State private var email: String = ""
@State private var password: String = ""
@State private var isLoading: Bool = false
@State private var errorMessage: String?
var body: some View {
Form {
TextField("Email", text: $email)
SecureField("Password", text: $password)
Button("登录") {
login()
}.disabled(isLoading)
}
}
private func login() {
isLoading = true
// 执行网络请求
}
}
在示例中有四个 @State 属性:email 和 password 用于表单字段,isLoading 用于加载指示,errorMessage 用于显示错误。每个属性独立管理自己那部分界面。当 isLoading 改变时,按钮通过 disabled(isLoading) 自动锁定——无需手动更新 UI。
常见问题
@State 专用于特定视图的局部状态。private 修饰符确保其他组件无法直接修改它,从而破坏封装性。外部访问请使用 $ 投影。
可以,@State 支持数组和字典,因为它们是值类型。然而,当修改数组元素时,SwiftUI 会重绘整个视图。对于大型列表,使用 @StateObject 配合 @Published 更高效。
@State 能正确处理 Optional 类型。当赋值为 nil 时,SwiftUI 会检测到变化并重绘视图。这对于像 errorMessage: String? 这样的状态很方便,其中 nil 表示没有错误。
@State 只要视图保持在层次结构中就会保留值。如果视图从层次结构中移除并重新添加,@State 将使用默认值重新初始化。持久化请使用 @AppStorage。
可以,将变化包裹在 withAnimation 中:withAnimation(.easeInOut) { isExpanded.toggle() }。SwiftUI 会使用指定的动画类型对界面新旧状态之间的过渡进行动画处理。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。