概述
状态管理是 SwiftUI 应用的核心。本章将系统介绍从 iOS 13 到 iOS 17+ 的所有状态管理技术,包括传统的 ObservableObject 系列和现代的 @Observable 宏,帮助你根据项目需求选择最合适的方案。
第一部分:基础状态管理(iOS 13+)
1. @State:本地视图状态
@State 用于管理视图内部的简单状态,当值改变时自动刷新 UI。
1struct CounterView: View { 2 @State private var count = 0 3 4 var body: some View { 5 VStack { 6 Text("Count: \(count)") 7 Button("Increment") { count += 1 } 8 } 9 } 10} 11
要点:
- 标记为
private,仅当前视图使用 - 适合
Int、String、Bool等简单类型 - 当状态变化时,SwiftUI 重新计算
body
2. @Binding:父子视图双向绑定
@Binding 创建对现有状态的引用,允许子视图修改父视图的状态。
1struct ParentView: View { 2 @State private var isOn = false 3 4 var body: some View { 5 ToggleView(isOn: $isOn) // 传递绑定 6 } 7} 8 9struct ToggleView: View { 10 @Binding var isOn: Bool 11 12 var body: some View { 13 Toggle("开关", isOn: $isOn) 14 } 15} 16
第二部分:传统响应式状态管理(iOS 13+)
3. ObservableObject 协议与 @Published
ObservableObject 用于创建可观察的类,@Published 标记需要通知视图的属性。
1import Combine 2 3class UserViewModel: ObservableObject { 4 @Published var name = "张三" 5 @Published var age = 25 6 7 func updateName(_ newName: String) { 8 name = newName 9 } 10} 11
4. @StateObject vs @ObservedObject
| 特性 | @StateObject | @ObservedObject |
|---|---|---|
| 生命周期 | 视图创建时初始化一次 | 随视图重建而重建 |
| 所有权 | 拥有对象 | 仅观察外部对象 |
| 适用场景 | 视图的主要数据源 | 从父视图传入的对象 |
1struct ContentView: View { 2 @StateObject private var viewModel = UserViewModel() // 拥有 3 4 var body: some View { 5 ChildView(viewModel: viewModel) // 传递 6 } 7} 8 9struct ChildView: View { 10 @ObservedObject var viewModel: UserViewModel // 观察 11 12 var body: some View { 13 Text(viewModel.name) 14 } 15} 16
5. @EnvironmentObject:全局共享状态
通过环境在任意层级共享对象,避免逐层传递。
1class AppState: ObservableObject { 2 @Published var isLoggedIn = false 3} 4 5@main 6struct MyApp: App { 7 @StateObject private var appState = AppState() 8 9 var body: some Scene { 10 WindowGroup { 11 ContentView() 12 .environmentObject(appState) 13 } 14 } 15} 16 17struct ProfileView: View { 18 @EnvironmentObject var appState: AppState 19 20 var body: some View { 21 Text(appState.isLoggedIn ? "已登录" : "未登录") 22 } 23} 24
6. @Environment:系统环境值
访问系统提供的环境值,如颜色方案、尺寸类等。
1struct ThemeAwareView: View { 2 @Environment(\.colorScheme) var colorScheme 3 @Environment(\.horizontalSizeClass) var horizontalSizeClass 4 5 var body: some View { 6 Text("当前模式: \(colorScheme == .dark ? "深色" : "浅色")") 7 } 8} 9
7. @AppStorage:持久化存储
使用 UserDefaults 自动持久化简单数据。
1struct SettingsView: View { 2 @AppStorage("username") var username = "" 3 @AppStorage("isDarkMode") var isDarkMode = false 4 5 var body: some View { 6 TextField("用户名", text: $username) 7 Toggle("深色模式", isOn: $isDarkMode) 8 } 9} 10
8. @SceneStorage:场景持久化
在场景(如多窗口)中保持状态,窗口关闭后自动清除。
1struct DocumentView: View { 2 @SceneStorage("scrollPosition") var scrollPosition: Double = 0 3 4 var body: some View { 5 ScrollView { 6 // 内容 7 } 8 } 9} 10
第三部分:现代状态管理(iOS 17+)
9. @Observable 宏
iOS 17 引入 @Observable 宏,简化了可观察对象的创建,无需 ObservableObject 和 @Published。
1import SwiftUI 2 3@Observable 4class UserModel { 5 var name = "张三" 6 var age = 25 7 var email = "zhangsan@example.com" 8} 9 10struct ContentView: View { 11 @State private var userModel = UserModel() 12 13 var body: some View { 14 VStack { 15 Text("姓名: \(userModel.name)") 16 TextField("修改姓名", text: $userModel.name) // 直接使用 $ 绑定 17 } 18 } 19} 20
优势:
- 语法更简洁,无需协议和属性包装器
- 所有属性默认可观察
- 性能更优(直接访问)
10. @Bindable 双向绑定
当需要将 @Observable 对象的属性传递给需要绑定的子视图时,使用 @Bindable。
1@Observable 2class Settings { 3 var isDarkMode = false 4} 5 6struct ParentView: View { 7 @State private var settings = Settings() 8 9 var body: some View { 10 ChildView(settings: settings) // 直接传递 11 } 12} 13 14struct ChildView: View { 15 @Bindable var settings: Settings // 添加 @Bindable 16 17 var body: some View { 18 Toggle("深色模式", isOn: $settings.isDarkMode) // 可绑定 19 } 20} 21
11. 使用 @Environment 与 @Observable 结合
现代方式也可以将可观察对象放入环境。
1@Observable 2class AppState { 3 var isLoggedIn = false 4 var userName = "" 5} 6 7@main 8struct MyApp: App { 9 @State private var appState = AppState() 10 11 var body: some Scene { 12 WindowGroup { 13 ContentView() 14 .environment(appState) // 注入环境 15 } 16 } 17} 18 19struct ProfileView: View { 20 @Environment(AppState.self) private var appState // 读取环境 21 22 var body: some View { 23 Text(appState.isLoggedIn ? "欢迎 \(appState.userName)" : "未登录") 24 } 25} 26
第四部分:最佳实践与迁移指南
选择合适的状态管理工具
| 场景 | 推荐方式(iOS 13-16) | 推荐方式(iOS 17+) |
|---|---|---|
| 单个视图内部状态 | @State | @State |
| 父子视图共享 | @Binding | @Binding |
| 复杂业务逻辑 | @StateObject + ObservableObject | @State + @Observable |
| 全局共享状态 | @EnvironmentObject | @Environment + @Observable |
| 持久化简单数据 | @AppStorage | @AppStorage |
| 场景临时状态 | @SceneStorage | @SceneStorage |
从 ObservableObject 迁移到 @Observable
迁移步骤:
- 将
class SomeModel: ObservableObject改为@Observable class SomeModel - 移除所有
@Published包装器 - 将
@StateObject改为@State(如果对象是视图拥有的) - 将
@ObservedObject改为@Bindable(如果需要双向绑定) - 将
@EnvironmentObject改为@Environment(SomeModel.self)
迁移示例:
1// 旧方式 2class OldViewModel: ObservableObject { 3 @Published var text = "" 4} 5struct OldView: View { 6 @StateObject private var vm = OldViewModel() 7 var body: some View { TextField("", text: $vm.text) } 8} 9 10// 新方式 11@Observable 12class NewViewModel { 13 var text = "" 14} 15struct NewView: View { 16 @State private var vm = NewViewModel() 17 var body: some View { TextField("", text: $vm.text) } 18} 19
第五部分:实战:完整的待办事项应用(双版本对比)
传统方式(ObservableObject)
1import SwiftUI 2import Combine 3 4class TodoViewModel: ObservableObject { 5 @Published var todos: [Todo] = [] 6 @Published var newTitle = "" 7 8 struct Todo: Identifiable { 9 let id = UUID() 10 var title: String 11 var isCompleted = false 12 } 13 14 func addTodo() { 15 guard !newTitle.isEmpty else { return } 16 todos.append(Todo(title: newTitle)) 17 newTitle = "" 18 } 19 20 func toggle(_ todo: Todo) { 21 if let idx = todos.firstIndex(where: { $0.id == todo.id }) { 22 todos[idx].isCompleted.toggle() 23 } 24 } 25} 26 27struct TodoListView: View { 28 @StateObject private var viewModel = TodoViewModel() 29 30 var body: some View { 31 NavigationStack { 32 VStack { 33 HStack { 34 TextField("新待办", text: $viewModel.newTitle) 35 .textFieldStyle(.roundedBorder) 36 Button("添加") { viewModel.addTodo() } 37 } 38 .padding() 39 List { 40 ForEach(viewModel.todos) { todo in 41 HStack { 42 Image(systemName: todo.isCompleted ? "checkmark.circle.fill" : "circle") 43 .onTapGesture { viewModel.toggle(todo) } 44 Text(todo.title) 45 } 46 } 47 } 48 } 49 .navigationTitle("待办事项") 50 } 51 } 52} 53
现代方式(@Observable)
1import SwiftUI 2 3@Observable 4class TodoViewModel { 5 var todos: [Todo] = [] 6 var newTitle = "" 7 8 struct Todo: Identifiable { 9 let id = UUID() 10 var title: String 11 var isCompleted = false 12 } 13 14 func addTodo() { 15 guard !newTitle.isEmpty else { return } 16 todos.append(Todo(title: newTitle)) 17 newTitle = "" 18 } 19 20 func toggle(_ todo: Todo) { 21 if let idx = todos.firstIndex(where: { $0.id == todo.id }) { 22 todos[idx].isCompleted.toggle() 23 } 24 } 25} 26 27struct TodoListView: View { 28 @State private var viewModel = TodoViewModel() 29 30 var body: some View { 31 NavigationStack { 32 VStack { 33 HStack { 34 TextField("新待办", text: $viewModel.newTitle) 35 .textFieldStyle(.roundedBorder) 36 Button("添加") { viewModel.addTodo() } 37 } 38 .padding() 39 List { 40 ForEach(viewModel.todos) { todo in 41 HStack { 42 Image(systemName: todo.isCompleted ? "checkmark.circle.fill" : "circle") 43 .onTapGesture { viewModel.toggle(todo) } 44 Text(todo.title) 45 } 46 } 47 } 48 } 49 .navigationTitle("待办事项") 50 } 51 } 52} 53
总结
SwiftUI 提供了从基础到高级的完整状态管理方案:
- 基础层:
@State、@Binding– 适用于简单、局部的状态 - 传统响应式层:
ObservableObject、@Published、@StateObject、@ObservedObject、@EnvironmentObject– 适用于 iOS 13-16 的复杂状态管理 - 持久化层:
@AppStorage、@SceneStorage– 适用于数据持久化 - 现代层(iOS 17+):
@Observable、@Bindable– 更简洁、更高效,推荐新项目使用
选择建议:
- 新项目且最低支持 iOS 17:优先使用
@Observable+@Environment - 需要兼容 iOS 16 及以下:继续使用
ObservableObject系列 - 两者可以在同一项目中共存,逐步迁移
掌握这些工具,你将能够构建出响应迅速、结构清晰的 SwiftUI 应用。
参考资料
- Apple Documentation: State
- Apple Documentation: ObservableObject
- Apple Documentation: @Observable
- WWDC 2023: Discover Observation in SwiftUI
- Swift by Sundell: State management in SwiftUI
本内容为《SwiftUI 进阶》第9章,涵盖从基础到现代的全部状态管理技术。欢迎关注后续更新。
《《swiftUI进阶 第9章SwiftUI 状态管理完全指南》》 是转载文章,点击查看原文。