《swiftUI进阶 第9章SwiftUI 状态管理完全指南》

作者:90后晨仔日期:2026/4/18

概述

状态管理是 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,仅当前视图使用
  • 适合 IntStringBool 等简单类型
  • 当状态变化时,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

迁移步骤

  1. class SomeModel: ObservableObject 改为 @Observable class SomeModel
  2. 移除所有 @Published 包装器
  3. @StateObject 改为 @State(如果对象是视图拥有的)
  4. @ObservedObject 改为 @Bindable(如果需要双向绑定)
  5. @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 应用。


参考资料


本内容为《SwiftUI 进阶》第9章,涵盖从基础到现代的全部状态管理技术。欢迎关注后续更新。


《swiftUI进阶 第9章SwiftUI 状态管理完全指南》》 是转载文章,点击查看原文


相关推荐


Nginx 从入门到精通:全面解析与实战指南
程序员果子2026/4/10

目录 前言:为什么要学 Nginx? 一、Nginx 基础入门:从零搭建第一个服务 1.1 初识 Nginx:它是什么,能做什么? 1.2 第一个 Nginx 服务:最小化配置实战 1.3 安装:Linux 里的 Nginx 魔法:从下载到部署,轻松拿捏! 二、核心架构与配置解析:读懂 Nginx 的 "运行逻辑" 2.1 架构精髓:Master-Worker 进程模型 2.2 配置骨架:从 http 到 location 的层级关系 2.3 静态资源服务:Nginx 的 "原


多Agent工作流开发
字节逆旅2026/4/2

最近 OpenClaw、Claude Code 特别火,我当时就在想,能不能写个自己的Agent,让它自动根据我的需求文件干活?比如我改个 tasks.md,它就自动跳出来把代码写了。这不比怼着ide开发高级多了? 最开始的想法非常简单粗暴:用 Node.js 写个脚本,利用 chokidar 盯着一个 todo 文件夹。只要文件一变,脚本就通过 child_process 里的 exec 去调 claude 命令。 初版脚本核心逻辑: const chokidar = require('cho


利用 Cloudflare 邮件路由实现无限子邮箱配置指南
墨风如雪2026/3/24

上几期文章我介绍了怎么把域名托管到CLoudFlare和免费白嫖CF CDN的操作,这次我演示的是我日常最喜欢的功能之一,邮箱路由功能。可以只需要一个域名就可以拥有属于自己的邮箱,而且可以创建无限的子邮箱提供使用。 在这里你不需要搭建复杂的邮局,只要你有一个托管在 Cloudflare 的域名,就可以用任意的前缀邮箱来注册你想要的账号,所有邮件都会自动转发到你指定的主邮箱里面,接收验证码会非常的方便。 强大的开源资源库 在正式配置之前,我这里先介绍一个收集了十分多使用CLoudFlare免费资源


告别登录中断:前端双 Token无感刷新
发现一只大呆瓜2026/3/16

前言 在前后端分离的项目中,为了安全,Token 通常会设置有效期。但如果 Token 过期时强制用户重新登录,会极大地破坏用户体验。如何做到在用户毫无察觉的情况下,自动完成 Token 的续期?本文将深度拆解 “双 Token 无感刷新” 的实现机制。 一、 为什么需要“无感刷新”? 举个简单例子,你正在某 App 编辑内容,中途切出几分钟,再切回来时,直接弹出登录页,提示“登录已过期,请重新登录”,这种场景很容易让用户流失。 传统的单 Token 方案存在一个两难境地: 有效期过短:用户操


构建无障碍组件之Switch Pattern
anOnion2026/3/8

Switch Pattern 详解:构建无障碍开关组件 开关(Switch)是一种模拟物理开关的控件,用于在两个状态(通常是"开"和"关")之间切换。在一些 UI 组件库中,它也被称为 Toggle(切换开关)。本文基于 W3C WAI-ARIA Switch Pattern 规范,详解如何构建无障碍的开关组件。 一、Switch 的定义与核心概念 1.1 什么是 Switch Switch 是一种特殊的二元状态控件,它: 模拟物理开关的行为 在两个互斥状态之间切换(开/关、启用/禁用) 与


纯 CSS 实现弹性文字效果
掘金安东尼2026/2/28

原文:How to Create a CSS-only Elastic Text Effect 翻译:TUARAN 欢迎关注 前端周刊,每周更新国外论坛的前端热门文章,紧跟时事,掌握前端技术动态。 每个字母单独动画的文字效果总是很酷、很吸睛。这类错峰动画通常依赖 JavaScript 库实现,对我们要实现的这种相对轻量的设计效果来说,代码往往偏重。本文将探索只用 CSS、无需 JavaScript 实现 fancy 文字效果的技巧(意味着需要手动拆分字符)。 截至撰写时,仅 Chrome 和


LiteOps:轻量级CI/CD平台,重塑开发运维新体验
YL_jia2026/2/19

LiteOps:轻量级CI/CD平台,重塑开发运维新体验 在效率至上的时代,LiteOps正以“简洁易用”和“开箱即用”的理念,重新定义自动化部署流程。 一、LiteOps:为何成为开发运维的新宠? 在软件开发的快速迭代中,持续集成和持续部署(CI/CD) 已成为提升开发效率和软件质量的关键手段。 然而传统CI/CD工具往往配置复杂、学习曲线陡峭。LiteOps作为一个专注于实用性的轻量级CI/CD平台,应运而生,它开源免费,能够为开发团队提供高效、便捷的自动化构建和部署解决方案。 1.1


BLE协议栈:链路层与ATT/L2CAP的交互详解
mftang2026/2/11

目录 概述 1  整体交互架构概览 1.1 交互流程总览 1.2 数据平面:PDU传输流程 1.2.1  发送路径:从ATT到空中 1.2.2 接收路径:从空中到ATT 1.3 控制平面:连接与参数管理 1.3.1 连接生命周期交互 1.3.2 关键参数协商流程 1.4 事件与通知机制 1.4.1 链路层事件驱动模型 1.4.2  ATT通知/指示与链路层交互 1.5 性能优化交互 1.5.1 数据长度扩展交互(BLE 4.2+) 1.5.2 信道选择算法 1


2026 年或许是中国 AI 社交的元年~
苍何2026/2/2

这是苍何的第 478 篇原创! 大家好,我是苍何。 网上你们经常在哪儿聊天啊? 我妈说在抖音,我弟说在陌陌,但其实我们都是在微信里聊的这个问题。 我自己建了几十个微信群,还加入了一堆的群聊,每天光看信息,就得费我半天老命。 为此,我基于 Ipad 协议开发过微信 AI 助手,能总结群聊消息,能帮回复问题,帮做任务,2021 年卖给了麻省理工学院社团。 一开始,我以为,我能用 AI 助手解决这些问题,但后面,还是避免不了被各种封号,最痛苦的是,我的群直接就被一锅端了。 所以,后面,我没再折腾微信


多网卡如何区分路由,使用宽松模式测试网络
venus602026/1/23

一、什么是 Linux 的“非对称路由” 1️⃣ 定义(先给结论) 非对称路由指的是: 数据包从 A 网卡进来,但回包却从 B 网卡出去 在多网卡、多出口服务器上非常常见,比如: 双网卡 多默认网关 同一台服务器连多个网络 你之前的情况就是典型的非对称路由。 2️⃣ Linux 默认为什么不喜欢非对称路由? 因为它可能意味着: IP 欺骗(spoofing) 流量劫持 路由异常 所以 Linux 默认启用了一个安全机制: 👉

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 XYZ博客