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