第5章:基础状态管理
![]()
示例代码都放这里啦,有需要的可以下载学习。swiftUIDemo
5.1 @State:本地视图状态
@State 介绍
@State 是 SwiftUI 中最基本的状态管理工具,用于管理视图的本地状态。它是一个属性包装器,允许我们在结构体中创建可变状态。
基本用法
import SwiftUI
struct CounterView: View {
// 使用 @State 声明本地状态
@State private var count = 0
var body: some View {
VStack(spacing: 20) {
// 显示状态值
Text("Count: \(count)")
.font(.largeTitle)
.fontWeight(.bold)
// 修改状态
Button("Increment") {
count += 1 // 状态改变,UI 自动更新
}
.buttonStyle(.borderedProminent)
Button("Reset") {
count = 0 // 状态重置
}
.buttonStyle(.bordered)
}
.padding()
}
}
#Preview {
CounterView()
}
工作原理
@State 的工作原理:
- 当你使用
@State标记一个属性时,SwiftUI 会在底层为这个属性创建一个独立的存储 - 这个存储不受结构体值类型特性的影响,即使结构体被重新创建,状态也会保持
- 当状态值改变时,SwiftUI 会自动重新计算视图的
body属性 - 系统会对比新旧视图树,只更新发生变化的部分
最佳实践
-
标记为 private:
@State应该只在当前视图内部使用,所以应该标记为private -
初始值:必须为
@State属性提供初始值 -
避免在 body 中修改:不要在
body计算属性中直接修改@State值 -
简单类型:
@State适合存储简单类型(如 Bool、Int、String 等)
5.2 @Binding:父子视图双向绑定
@Binding 介绍
@Binding 用于在父子视图之间创建双向绑定,允许子视图修改父视图的状态。
基本用法
import SwiftUI
// 父视图
struct ParentView: View {
// 父视图的状态
@State private var isPlaying = false
var body: some View {
VStack(spacing: 20) {
Text("Parent View")
.font(.headline)
Text("Is Playing: \(isPlaying ? "Yes" : "No")")
// 使用 $ 符号创建绑定并传递给子视图
PlayButton(isPlaying: $isPlaying)
}
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(8)
}
}
// 子视图
struct PlayButton: View {
// 使用 @Binding 接收父视图的状态引用
@Binding var isPlaying: Bool
var body: some View {
Button(isPlaying ? "Pause" : "Play") {
// 修改绑定值,会同步更新父视图的状态
isPlaying.toggle()
}
.buttonStyle(.borderedProminent)
.tint(isPlaying ? .red : .green)
.padding()
.background(Color.gray.opacity(0.05))
.cornerRadius(8)
}
}
#Preview {
ParentView()
}
工作原理
@Binding 的工作原理:
- 它不是存储状态,而是创建一个对现有状态的引用
- 当子视图修改绑定值时,实际上是修改了原始的
@State状态 - 状态的所有权仍然在父视图中
- 这种机制确保了单一数据源(SSOT)原则
实际应用
// 表单输入示例
struct FormView: View {
@State private var username = ""
@State private var email = ""
var body: some View {
VStack(spacing: 16) {
Text("User Form")
.font(.headline)
TextFieldView(
title: "Username",
text: $username,
placeholder: "Enter your username"
)
TextFieldView(
title: "Email",
text: $email,
placeholder: "Enter your email"
)
Text("Username: \(username)")
Text("Email: \(email)")
}
.padding()
}
}
struct TextFieldView: View {
let title: String
@Binding var text: String
let placeholder: String
var body: some View {
VStack(alignment: .leading, spacing: 4) {
Text(title)
.font(.subheadline)
.fontWeight(.medium)
TextField(
placeholder,
text: $text
)
.textFieldStyle(.roundedBorder)
}
}
}
5.3 @StateObject:可观察对象状态
@StateObject 介绍
@StateObject 用于管理符合 ObservableObject 协议的对象,适用于需要在多个视图之间共享的复杂状态。
基本用法
import SwiftUI
import Combine
// 可观察对象模型
class UserViewModel: ObservableObject {
// 使用 @Published 标记需要发布的属性
@Published var username = ""
@Published var email = ""
@Published var isLoggedIn = false
func login() {
// 模拟登录操作
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.isLoggedIn = true
}
}
func logout() {
username = ""
email = ""
isLoggedIn = false
}
}
struct UserView: View {
// 使用 @StateObject 管理可观察对象
@StateObject private var viewModel = UserViewModel()
var body: some View {
VStack(spacing: 20) {
Text("User Profile")
.font(.headline)
if viewModel.isLoggedIn {
Text("Welcome, \(viewModel.username)!")
.font(.title)
Text("Email: \(viewModel.email)")
Button("Logout") {
viewModel.logout()
}
.buttonStyle(.borderedProminent)
.tint(.red)
} else {
TextField("Username", text: $viewModel.username)
.textFieldStyle(.roundedBorder)
TextField("Email", text: $viewModel.email)
.textFieldStyle(.roundedBorder)
Button("Login") {
viewModel.login()
}
.buttonStyle(.borderedProminent)
.disabled(viewModel.username.isEmpty || viewModel.email.isEmpty)
}
}
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(8)
}
}
#Preview {
UserView()
}
工作原理
@StateObject 的工作原理:
- 它会创建并拥有一个符合
ObservableObject协议的对象 - 当对象的
@Published属性改变时,所有使用该对象的视图都会自动更新 - 即使视图被重新创建,
@StateObject也会保持对象的生命周期 - 适用于需要在多个视图之间共享的复杂状态
最佳实践
- 用于复杂状态:适用于包含多个相关属性的复杂状态
- 单一数据源:作为状态的唯一来源
- 生命周期管理:由 SwiftUI 管理对象的生命周期
- 性能考虑:对于大型对象,考虑使用更细粒度的状态管理
5.4 @ObservedObject:观察外部对象
@ObservedObject 介绍
@ObservedObject 用于观察外部传入的符合 ObservableObject 协议的对象,适用于从父视图传递的可观察对象。
基本用法
import SwiftUI
// 父视图
struct ParentWithObservedObject: View {
// 父视图拥有状态对象
@StateObject private var userViewModel = UserViewModel()
var body: some View {
VStack(spacing: 20) {
Text("Parent View")
.font(.headline)
// 传递给子视图
ChildView(viewModel: userViewModel)
}
.padding()
}
}
// 子视图
struct ChildView: View {
// 使用 @ObservedObject 观察外部对象
@ObservedObject var viewModel: UserViewModel
var body: some View {
VStack(spacing: 16) {
Text("Child View")
.font(.subheadline)
TextField("Username", text: $viewModel.username)
.textFieldStyle(.roundedBorder)
TextField("Email", text: $viewModel.email)
.textFieldStyle(.roundedBorder)
Button("Login") {
viewModel.login()
}
.buttonStyle(.borderedProminent)
}
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(8)
}
}
#Preview {
ParentWithObservedObject()
}
工作原理
@ObservedObject 的工作原理:
- 它不拥有对象,只是观察外部传入的对象
- 当对象的
@Published属性改变时,视图会自动更新 - 对象的生命周期由其拥有者管理
- 适用于从父视图传递的可观察对象
与 @StateObject 的区别
| 特性 | @StateObject | @ObservedObject |
|---|---|---|
| 所有权 | 拥有对象,管理生命周期 | 观察对象,不管理生命周期 |
| 初始化 | 在视图中直接初始化 | 从外部传入 |
| 适用场景 | 作为状态的唯一来源 | 观察父视图传递的对象 |
| 性能 | 更高效,避免重复创建 | 可能会因父视图重建而重复创建 |
5.5 @EnvironmentObject:全局环境对象
@EnvironmentObject 介绍
@EnvironmentObject 用于访问通过环境传递的全局可观察对象,适用于跨多个视图层级共享的状态。
基本用法
import SwiftUI
// 全局状态模型
class AppState: ObservableObject {
@Published var isDarkMode = false
@Published var userLanguage = "zh"
func toggleDarkMode() {
isDarkMode.toggle()
}
func changeLanguage(to language: String) {
userLanguage = language
}
}
// 主视图 - 设置环境对象
struct MainView: View {
@StateObject private var appState = AppState()
var body: some View {
NavigationStack {
VStack {
Text("Main View")
.font(.headline)
NavigationLink("Settings", destination: SettingsView())
NavigationLink("Profile", destination: ProfileView())
}
.padding()
}
// 通过环境传递对象
.environmentObject(appState)
}
}
// 设置视图 - 访问环境对象
struct SettingsView: View {
// 通过 @EnvironmentObject 访问全局对象
@EnvironmentObject private var appState: AppState
var body: some View {
VStack(spacing: 20) {
Text("Settings")
.font(.headline)
Toggle("Dark Mode", isOn: $appState.isDarkMode)
Picker("Language", selection: $appState.userLanguage) {
Text("English").tag("en")
Text("中文").tag("zh")
Text("日本語").tag("ja")
}
.pickerStyle(.segmented)
}
.padding()
.background(appState.isDarkMode ? Color.black : Color.white)
.foregroundColor(appState.isDarkMode ? Color.white : Color.black)
}
}
// 个人资料视图 - 访问环境对象
struct ProfileView: View {
@EnvironmentObject private var appState: AppState
var body: some View {
VStack(spacing: 20) {
Text("Profile")
.font(.headline)
Text("Current Language: \(appState.userLanguage)")
Text("Dark Mode: \(appState.isDarkMode ? "On" : "Off")")
}
.padding()
.background(appState.isDarkMode ? Color.black : Color.white)
.foregroundColor(appState.isDarkMode ? Color.white : Color.black)
}
}
#Preview {
MainView()
}
工作原理
@EnvironmentObject 的工作原理:
- 它从环境中查找指定类型的可观察对象
- 当对象的
@Published属性改变时,所有使用该对象的视图都会自动更新 - 不需要手动传递对象,通过环境自动注入
- 适用于跨多个视图层级共享的全局状态
最佳实践
- 全局状态:用于应用级别的全局状态
- 依赖注入:通过环境进行依赖注入,避免层层传递
- 类型安全:基于类型查找,确保类型正确
- 错误处理:确保在使用前在环境中设置了对象
5.6 @Environment:环境值
@Environment 介绍
@Environment 用于访问 SwiftUI 环境中的系统值,如布局方向、颜色方案、字体大小等。
基本用法
import SwiftUI
struct EnvironmentValuesView: View {
// 访问环境值
@Environment(\.colorScheme) private var colorScheme
@Environment(\.layoutDirection) private var layoutDirection
@Environment(\.dynamicTypeSize) private var dynamicTypeSize
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
var body: some View {
VStack(spacing: 16) {
Text("Environment Values")
.font(.headline)
Text("Color Scheme: \(colorScheme == .dark ? "Dark" : "Light")")
Text("Layout Direction: \(layoutDirection == .leftToRight ? "LTR" : "RTL")")
Text("Dynamic Type Size: \(dynamicTypeSize.description)")
Text("Horizontal Size Class: \(horizontalSizeClass == .regular ? "Regular" : "Compact")")
// 根据环境值调整布局
if horizontalSizeClass == .regular {
HStack {
Text("Wide Layout")
Spacer()
Text("More Content")
}
} else {
VStack {
Text("Narrow Layout")
Text("Content Below")
}
}
}
.padding()
.background(colorScheme == .dark ? Color.black : Color.white)
.foregroundColor(colorScheme == .dark ? Color.white : Color.black)
}
}
#Preview {
EnvironmentValuesView()
}
常用环境值
| 环境值 | 类型 | 描述 |
|---|---|---|
\.colorScheme |
ColorScheme |
当前颜色方案(浅色/深色) |
\.layoutDirection |
LayoutDirection |
布局方向(LTR/RTL) |
\.dynamicTypeSize |
DynamicTypeSize |
动态字体大小 |
\.horizontalSizeClass |
UserInterfaceSizeClass? |
水平尺寸类 |
\.verticalSizeClass |
UserInterfaceSizeClass? |
垂直尺寸类 |
\.locale |
Locale |
当前区域设置 |
\.calendar |
Calendar |
当前日历 |
\.timeZone |
TimeZone |
当前时区 |
\.accessibilityEnabled |
Bool |
是否启用辅助功能 |
\.scenePhase |
ScenePhase |
场景阶段(活跃/非活跃/背景) |
工作原理
@Environment 的工作原理:
- 它从 SwiftUI 环境中读取指定的环境值
- 当环境值改变时,视图会自动更新
- 环境值由系统或父视图设置
- 适用于响应系统设置和环境变化
5.7 @SceneStorage:场景存储
@SceneStorage 介绍
@SceneStorage 用于在场景级别持久化存储简单数据,适用于保存用户偏好设置和状态。
基本用法
import SwiftUI
struct SceneStorageView: View {
// 使用 @SceneStorage 存储数据
@SceneStorage("username") private var username = ""
@SceneStorage("isDarkMode") private var isDarkMode = false
@SceneStorage("counter") private var counter = 0
var body: some View {
VStack(spacing: 20) {
Text("Scene Storage")
.font(.headline)
TextField("Username", text: $username)
.textFieldStyle(.roundedBorder)
Toggle("Dark Mode", isOn: $isDarkMode)
VStack {
Text("Counter: \(counter)")
HStack {
Button("Increment") {
counter += 1
}
.buttonStyle(.bordered)
Button("Reset") {
counter = 0
}
.buttonStyle(.bordered)
}
}
Text("Note: Data persists across app restarts")
.font(.caption)
.foregroundColor(.secondary)
}
.padding()
.background(isDarkMode ? Color.black : Color.white)
.foregroundColor(isDarkMode ? Color.white : Color.black)
}
}
#Preview {
SceneStorageView()
}
工作原理
@SceneStorage 的工作原理:
- 它将数据存储在场景的 UserDefaults 中
- 数据会在场景重启后保持
- 每个场景有自己的存储,不同场景之间数据隔离
- 适用于存储用户偏好设置和临时状态
最佳实践
- 简单数据:适合存储简单类型(String、Int、Bool 等)
- 场景隔离:每个场景有独立的存储
- 自动持久化:数据自动保存,无需手动管理
- 键名唯一性:使用唯一的键名避免冲突
5.8 @AppStorage:应用存储
@AppStorage 介绍
@AppStorage 用于在应用级别持久化存储简单数据,适用于保存全局用户偏好设置。
基本用法
import SwiftUI
struct AppStorageView: View {
// 使用 @AppStorage 存储数据
@AppStorage("userName") private var userName = "Guest"
@AppStorage("appTheme") private var appTheme = "light"
@AppStorage("notificationsEnabled") private var notificationsEnabled = true
var body: some View {
VStack(spacing: 20) {
Text("App Storage")
.font(.headline)
TextField("User Name", text: $userName)
.textFieldStyle(.roundedBorder)
Picker("Theme", selection: $appTheme) {
Text("Light").tag("light")
Text("Dark").tag("dark")
Text("Auto").tag("auto")
}
.pickerStyle(.segmented)
Toggle("Enable Notifications", isOn: $notificationsEnabled)
Text("Note: Data persists across app reinstalls")
.font(.caption)
.foregroundColor(.secondary)
}
.padding()
.background(getThemeColor())
.foregroundColor(appTheme == "dark" ? Color.white : Color.black)
}
private func getThemeColor() -> Color {
switch appTheme {
case "dark":
return Color.black
case "light":
return Color.white
default:
return Color.white
}
}
}
#Preview {
AppStorageView()
}
工作原理
@AppStorage 的工作原理:
- 它将数据存储在应用的 UserDefaults 中
- 数据会在应用重启后保持
- 所有场景共享相同的存储
- 适用于存储全局用户偏好设置
与 @SceneStorage 的区别
| 特性 | @AppStorage | @SceneStorage |
|---|---|---|
| 存储范围 | 应用级别,所有场景共享 | 场景级别,每个场景独立 |
| 持久化 | 持久化到 UserDefaults | 持久化到场景的 UserDefaults |
| 适用场景 | 全局偏好设置 | 场景特定状态 |
| 数据共享 | 跨场景共享 | 场景隔离 |
5.9 @FocusedValue:聚焦值
@FocusedValue 介绍
@FocusedValue 用于在视图层次结构中传递聚焦状态相关的值,适用于处理键盘焦点和上下文相关操作。
基本用法
import SwiftUI
// 定义聚焦值键
struct EditModeKey: FocusedValueKey {
typealias Value = Bool
}
// 扩展 FocusedValues
extension FocusedValues {
var isEditMode: EditModeKey.Value? {
get { self[EditModeKey.self] }
set { self[EditModeKey.self] = newValue }
}
}
struct FocusedValueView: View {
@State private var isEditMode = false
@State private var text = "Hello, SwiftUI"
var body: some View {
VStack(spacing: 20) {
Text("Focused Value")
.font(.headline)
// 设置聚焦值
TextField("Enter text", text: $text)
.textFieldStyle(.roundedBorder)
.focusedValue(\.isEditMode, true)
Button("Toggle Edit Mode") {
isEditMode.toggle()
}
.buttonStyle(.borderedProminent)
// 子视图访问聚焦值
FocusedChildView()
}
.padding()
.environment(\.isEditMode, isEditMode)
}
}
struct FocusedChildView: View {
// 访问聚焦值
@FocusedValue(\.isEditMode) private var isEditMode
var body: some View {
VStack {
Text("Child View")
.font(.subheadline)
Text("Edit Mode: \(isEditMode ?? false ? "On" : "Off")")
if isEditMode ?? false {
Text("Editing is enabled!")
.foregroundColor(.green)
}
}
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(8)
}
}
#Preview {
FocusedValueView()
}
工作原理
@FocusedValue 的工作原理:
- 它通过
FocusedValues字典传递值 - 当焦点变化时,聚焦值会自动更新
- 适用于与焦点相关的上下文信息
- 可以自定义聚焦值键
适用场景
- 键盘焦点:跟踪当前聚焦的视图
- 上下文操作:根据聚焦状态显示不同的操作
- 编辑模式:在编辑模式下显示额外的控件
- 工具栏配置:根据当前聚焦的内容配置工具栏
5.10 状态驱动 UI 更新原理
核心原理
SwiftUI 的核心设计哲学是状态驱动:
UI = f(State)
这意味着:
- UI 是状态的函数
- 当状态改变时,UI 会自动更新
- 给定相同的状态,总是渲染相同的 UI
更新流程
当状态改变时,SwiftUI 的更新流程如下:
- 状态改变:用户操作或其他因素导致状态值发生变化
- 检测变化:SwiftUI 检测到状态变化
-
重新计算 body:重新调用受影响视图的
body计算属性 - 构建新视图树:生成新的视图层次结构
- 对比差异:对比新旧视图树,找出变化的部分
- 更新 UI:只更新发生变化的部分,保持其他部分不变
性能优化
SwiftUI 的状态驱动机制本身就很高效,因为:
- 增量更新:只更新变化的部分
- 值类型:视图是轻量级的值类型,创建成本低
- 智能对比:使用高效的差异算法
- 批处理:合并多个状态更新,减少渲染次数
- 懒加载:只渲染可见的部分
5.11 状态管理最佳实践
1. 选择合适的状态管理工具
| 状态类型 | 推荐工具 | 适用场景 |
|---|---|---|
| 本地简单状态 | @State |
单个视图的内部状态 |
| 父子视图共享 | @Binding |
子视图需要修改父视图状态 |
| 复杂对象状态 | @StateObject |
多个属性的复杂状态 |
| 外部对象引用 | @ObservedObject |
观察父视图传递的对象 |
| 全局共享状态 | @EnvironmentObject |
跨多个视图的全局状态 |
| 系统环境值 | @Environment |
访问系统设置和环境 |
| 场景级持久化 | @SceneStorage |
场景特定的持久状态 |
| 应用级持久化 | @AppStorage |
全局用户偏好设置 |
| 聚焦相关状态 | @FocusedValue |
与焦点相关的上下文信息 |
2. 状态管理原则
- 单一数据源:每个状态应该有唯一的来源
- 状态提升:将状态提升到需要访问它的所有视图的共同父视图
- 最小化状态:只存储必要的状态,避免冗余
- 状态隔离:将相关状态组织在一起,避免混乱
- 可测试性:状态管理应该易于测试
- 性能考虑:对于大型状态,考虑使用更细粒度的更新
3. 性能优化技巧
-
使用 Equatable:为模型实现
Equatable协议,避免不必要的更新 - 视图分离:将复杂视图拆分为更小的子视图
-
@State 用于简单类型:
@State适合存储简单类型,复杂类型使用@StateObject -
避免在 body 中创建对象:不要在
body计算属性中创建新对象 - 使用 .id() 强制更新:当需要强制视图更新时使用
- 考虑使用 Combine:对于复杂的异步操作,使用 Combine 框架
实战:创建一个完整的状态管理示例
需求分析
创建一个包含多种状态管理技术的应用,包括:
- 本地状态管理
- 父子视图绑定
- 可观察对象
- 环境对象
- 持久化存储
代码实现
import SwiftUI
import Combine
// 全局应用状态
class AppState: ObservableObject {
@Published var isDarkMode = false
@Published var currentUser: User? = nil
func toggleTheme() {
isDarkMode.toggle()
}
func login(user: User) {
currentUser = user
}
func logout() {
currentUser = nil
}
}
// 用户模型
struct User: Identifiable, Equatable {
let id = UUID()
let name: String
let email: String
}
// 主应用视图
struct StateManagementDemo: View {
@StateObject private var appState = AppState()
@AppStorage("lastLoggedInUser") private var lastLoggedInUser = ""
var body: some View {
NavigationStack {
VStack(spacing: 20) {
Text("状态管理演示")
.font(.largeTitle)
.fontWeight(.bold)
// 主题切换
Toggle("深色模式", isOn: $appState.isDarkMode)
// 用户登录状态
if appState.currentUser != nil {
Text("欢迎, \(appState.currentUser?.name ?? "")!")
Button("退出登录") {
appState.logout()
lastLoggedInUser = ""
}
.buttonStyle(.borderedProminent)
.tint(.red)
} else {
LoginView()
}
// 导航链接
NavigationLink("计数器示例", destination: CounterView())
NavigationLink("待办事项示例", destination: TodoApp())
NavigationLink("环境对象示例", destination: EnvironmentObjectDemo())
}
.padding()
}
.environmentObject(appState)
.preferredColorScheme(appState.isDarkMode ? .dark : .light)
}
}
// 登录视图
struct LoginView: View {
@State private var name = ""
@State private var email = ""
@EnvironmentObject private var appState: AppState
@AppStorage("lastLoggedInUser") private var lastLoggedInUser = ""
var body: some View {
VStack(spacing: 16) {
TextField("姓名", text: $name)
.textFieldStyle(.roundedBorder)
TextField("邮箱", text: $email)
.textFieldStyle(.roundedBorder)
Button("登录") {
let user = User(name: name, email: email)
appState.login(user: user)
lastLoggedInUser = name
}
.buttonStyle(.borderedProminent)
.disabled(name.isEmpty || email.isEmpty)
if !lastLoggedInUser.isEmpty {
Text("上次登录: \(lastLoggedInUser)")
.font(.caption)
.foregroundColor(.secondary)
}
}
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(8)
}
}
// 计数器视图
struct CounterView: View {
@State private var count = 0
@SceneStorage("counterValue") private var storedCount = 0
var body: some View {
VStack(spacing: 20) {
Text("计数器")
.font(.headline)
Text("Count: \(count)")
.font(.largeTitle)
.fontWeight(.bold)
HStack(spacing: 16) {
Button("减1") {
count -= 1
storedCount = count
}
.buttonStyle(.bordered)
Button("重置") {
count = 0
storedCount = 0
}
.buttonStyle(.bordered)
Button("加1") {
count += 1
storedCount = count
}
.buttonStyle(.borderedProminent)
}
Text("场景存储值: \(storedCount)")
.font(.caption)
.foregroundColor(.secondary)
}
.padding()
.onAppear {
// 从场景存储恢复
count = storedCount
}
}
}
// 待办事项应用
struct TodoApp: View {
@State private var todos: [TodoItem] = [
TodoItem(title: "学习 SwiftUI 状态管理"),
TodoItem(title: "完成本章练习"),
TodoItem(title: "构建示例应用")
]
@State private var newTodoTitle = ""
var body: some View {
VStack {
HStack(spacing: 8) {
TextField("输入新的待办事项", text: $newTodoTitle)
.textFieldStyle(.roundedBorder)
Button("添加") {
addTodo()
}
.buttonStyle(.borderedProminent)
.disabled(newTodoTitle.isEmpty)
}
.padding()
List {
ForEach($todos) { $todo in
HStack {
Button(action: {
todo.isCompleted.toggle()
}) {
Image(systemName: todo.isCompleted ? "checkmark.circle.fill" : "circle")
.foregroundColor(todo.isCompleted ? .green : .gray)
}
.buttonStyle(.plain)
Text(todo.title)
.strikethrough(todo.isCompleted, color: .gray)
.foregroundColor(todo.isCompleted ? .secondary : .primary)
Spacer()
Button(action: {
deleteTodo(todo)
}) {
Image(systemName: "trash.fill")
.foregroundColor(.red)
}
.buttonStyle(.plain)
}
}
}
}
.navigationTitle("待办事项")
}
private func addTodo() {
guard !newTodoTitle.isEmpty else { return }
todos.append(TodoItem(title: newTodoTitle))
newTodoTitle = ""
}
private func deleteTodo(_ todo: TodoItem) {
if let index = todos.firstIndex(where: { $0.id == todo.id }) {
todos.remove(at: index)
}
}
}
// 待办事项模型
struct TodoItem: Identifiable, Equatable {
let id = UUID()
var title: String
var isCompleted = false
}
// 环境对象演示
struct EnvironmentObjectDemo: View {
@EnvironmentObject private var appState: AppState
var body: some View {
VStack(spacing: 20) {
Text("环境对象演示")
.font(.headline)
Text("当前主题: \(appState.isDarkMode ? "深色" : "浅色")")
Text("登录状态: \(appState.currentUser != nil ? "已登录" : "未登录")")
if let user = appState.currentUser {
Text("用户: \(user.name)")
Text("邮箱: \(user.email)")
}
Button("切换主题") {
appState.toggleTheme()
}
.buttonStyle(.borderedProminent)
}
.padding()
}
}
#Preview {
StateManagementDemo()
}
代码解析
-
AppState:使用
@StateObject管理全局应用状态 -
User:符合
Identifiable和Equatable协议的用户模型 - StateManagementDemo:主应用视图,设置环境对象
-
LoginView:使用
@State和@AppStorage管理登录状态 -
CounterView:使用
@State和@SceneStorage管理计数器 -
TodoApp:使用
@State管理待办事项列表 -
EnvironmentObjectDemo:使用
@EnvironmentObject访问全局状态
小结
本章详细介绍了 SwiftUI 中的状态管理系统,包括:
-
@State:用于管理视图的本地状态 -
@Binding:用于父子视图之间的双向绑定 -
@StateObject:用于管理可观察对象的状态 -
@ObservedObject:用于观察外部传入的可观察对象 -
@EnvironmentObject:用于访问全局环境对象 -
@Environment:用于访问系统环境值 -
@SceneStorage:用于场景级别的持久化存储 -
@AppStorage:用于应用级别的持久化存储 -
@FocusedValue:用于传递聚焦相关的值 - 状态驱动 UI 更新的原理
- 状态管理的最佳实践
- 一个完整的状态管理示例应用
通过本章的学习,你已经掌握了 SwiftUI 中所有的状态管理技术,能够根据不同的场景选择合适的状态管理工具,创建具有复杂交互功能的应用。
参考资料
- SwiftUI 官方文档
- Apple Developer Documentation: State
- Apple Developer Documentation: Binding
- Apple Developer Documentation: StateObject
- Apple Developer Documentation: ObservedObject
- Apple Developer Documentation: EnvironmentObject
- Apple Developer Documentation: Environment
- Apple Developer Documentation: SceneStorage
- Apple Developer Documentation: AppStorage
- Apple Developer Documentation: FocusedValue
- WWDC 2019: Data Flow Through SwiftUI
- WWDC 2020: Data Essentials in SwiftUI
- SwiftUI by Example
本内容为《SwiftUI 基础教程》第五章,欢迎关注后续更新。