SwiftUI 中的这些注解(专业术语叫 Property Wrappers / 属性包装器)是驱动界面刷新的核心引擎。
你可以把它们简单理解为:告诉 SwiftUI 某个变量的“身份”和“刷新规则”。
为了方便记忆,我把它们分为三大类:基础值类型、引用类型(对象)、环境与存储。
第一类:基础值类型 (Int, String, Bool, Struct)
用于管理简单的 UI 状态,比如开关、输入框文字、数字计数。
1. @State (私有状态)
- 身份: “房主”。
- 作用: View 内部私有的、由 View 自己创建和管理的变量。
- 刷新规则: 值改变 -> 当前 View 重新渲染。
-
场景: 按钮点击次数、弹窗是否显示、输入框的内容。
struct CounterView: View { @State private var count = 0 // View 自己管理这个状态 var body: some View { Button("Tap me: \(count)") { count += 1 } } }
2. @Binding (绑定 / 引用)
- 身份: “传声筒”。
- 作用: 它不存数据,它只是指向别人(通常是父视图)的
@State。它实现了双向绑定:子视图改了,父视图的数据也会变。 - 场景: 开关组件(Toggle)、输入框组件。
-
用法: 传参时需要加
$符号。struct ParentView: View { @State private var isOn = false var body: some View { // 把 $isOn 传给子视图,建立连接 ToggleView(isOn: $isOn) } } struct ToggleView: View { @Binding var isOn: Bool // 接收连接,不设初值 var body: some View { Toggle("Switch", isOn: $isOn) // 修改这里,ParentView 的 isOn 也会变 } }
第二类:引用类型 (Class / ViewModel)
用于管理复杂的逻辑和数据,通常遵循 ObservableObject 协议。
3. @StateObject (对象持有者)
- 身份: “钉子” (Owner)。
- 作用: 负责初始化和持有一个 ViewModel。即使 View 刷新,这个对象也不会死。
- 场景: 页面创建自己的 ViewModel 时使用。
@StateObject var vm = MyViewModel() // ✅ 这里有 = 号
4. @ObservedObject (对象观察者)
- 身份: “眼镜” (Observer)。
- 作用: 观察外部传入的 ViewModel。如果 View 刷新,它不保证对象存活(所以不能在这里初始化)。
- 场景: 子视图接收父视图传进来的 ViewModel。
@ObservedObject var vm: MyViewModel // ❌ 这里通常没有 = 号
5. @EnvironmentObject (全局/环境对象)
- 身份: “广播接收器”。
- 作用: 不需要一层层传参,直接从“空气”(环境)里抓取对象。前提是祖先视图必须先用
.environmentObject(vm)注入过。 - 场景: 用户登录信息、全局主题设置、像你的
SyncSettingsViewModel这种跨页面共享的数据。@EnvironmentObject var settings: SyncSettingsViewModel
6. @Published (发布者)
- 位置: 写在
class(ViewModel) 内部。 - 作用: 告诉系统:“当这个属性变化时,所有观察我的 View 都要刷新”。
class MyViewModel: ObservableObject { @Published var items: [String] = [] // items 变了,界面就变 }
第三类:持久化与环境系统
7. @AppStorage (UserDefaults 包装器)
- 作用: 自动读写
UserDefaults。修改变量会自动保存到本地,且自动刷新 UI。 - 场景: 简单的设置项(如你的
iCloudEnabled)。@AppStorage("username") var username: String = "Guest"
8. @Environment (系统环境值)
- 作用: 读取系统提供的环境变量。
- 场景: 获取当前是深色/浅色模式、关闭当前弹窗(dismiss)、获取核心数据上下文(managedObjectContext)。
@Environment(\.colorScheme) var colorScheme @Environment(\.dismiss) var dismiss // 调用 dismiss() 关闭页面
⚡️ 总结:我该用哪个?(决策速查表)
| 你的数据类型 | 数据来源 | 推荐注解 |
|---|---|---|
| Int, Bool, String | 当前 View 自己产生 | @State |
| Int, Bool, String | 父 View 传下来的,且需要修改 | @Binding |
| Int, Bool, String | 父 View 传下来的,只读 | 普通变量 let (无需注解) |
| Object (Class) | 当前 View 新建 (= ViewModel()) |
@StateObject |
| Object (Class) | 父 View 传参 (: ViewModel) |
@ObservedObject |
| Object (Class) | 隔代传递 / 全局共享 | @EnvironmentObject |
| 简单设置 | 需要存到 UserDefaults | @AppStorage |
💡 补充:iOS 17+ 的新变化 (Observation 框架)
如果你在看最新的 SwiftUI 教程(iOS 17+),你会发现上面的逻辑被简化了:
- 不再需要
ObservableObject和@Published,直接用宏@Observable标记 Class。 - View 里不再区分 StateObject/ObservedObject,统一用
@State或普通let/var。 - 这种新写法是未来的趋势,但目前为了兼容 iOS 16/15,上面的旧写法依然是主流。
你想看一个把这些都串起来的完整代码示例吗?