移动/游戏
构建或重构 SwiftUI 界面时如何组织导航、状态与组件
在新建或改造 SwiftUI 视图、组件、Tab 架构与响应式布局时,提供状态归属、导航、异步与预览的落地做法。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
构建或重构 SwiftUI 界面时如何组通用
解决思路
- 先判断目标:已有项目就定位功能/屏幕和主要交互模型(列表、详情、编辑器、设置、标签页),用 `rg "TabView\("` 等命令在仓库中找到相近示例并阅读最接近的 SwiftUI 视图;新项目则从 `references/app-wiring.md` 开始搭建 TabView + NavigationStack + sheets。
- 已有项目要沿用本地约定:优先使用 SwiftUI 原生状态,尽量把状态保持在局部,共享依赖用 environment 注入;再从 `references/components-index.md` 选择对应组件参考并遵循其指引。
- 如果交互是通过拖动或滚动把主内容移开来揭示次要内容,在手动实现手势前先读 `references/scroll-reveal.md`。
- 新项目脚手架:基于提供的骨架添加最小的 `AppTab` 和 `RouterPath`,按最先需要的 UI 选择下一个组件参考(TabView、NavigationStack、Sheets),新增屏幕时扩展 route 和 sheet 枚举。
- 用小型、聚焦的子视图和 SwiftUI 原生数据流来构建视图。
- 使用现代 SwiftUI 状态(`@State`、`@Binding`、`@Observable`、`@Environment`),避免不必要的 view model。
- 若部署目标包含 iOS 16 或更早、无法使用 iOS 17 的 Observation API,则回退到 `ObservableObject`:根部用 `@StateObject` 持有,注入观察用 `@ObservedObject`,只有真正全局共享的应用级状态才用 `@EnvironmentObject`。
- 优先组合;保持视图小而聚焦。
- 用 async/await 配合 `.task` 和显式的 loading/error 状态;涉及重启、取消和防抖时读 `references/async-state.md`。
- 共享应用服务放在 `@Environment`,但功能局部的依赖和模型优先用初始化器显式注入;根部接线模式读 `references/app-wiring.md`。
- 优先使用适配部署目标的最新 SwiftUI API,并在模式依赖某版本时标注最低 OS。
- 只在编辑遗留文件时保留既有遗留模式。
- 遵循项目的格式化和风格指南。
- Sheets:当状态表示被选中的模型时,优先用 `.sheet(item:)` 而非 `.sheet(isPresented:)`;避免在 sheet body 内使用 `if let`;sheet 应自己拥有动作并在内部调用 `dismiss()`,而不是转发 `onCancel`/`onConfirm` 闭包。
- 滚动驱动的揭示:优先从滚动偏移推导归一化进度值,并用这个单一事实来源驱动视觉状态;除非仅靠滚动无法表达交互,否则避免并行的手势状态机。
- 按所有权模型选择最窄的状态工具:单个视图拥有的局部 UI 状态用 `@State`;子视图修改父级拥有的值状态用 `@Binding`;iOS 17+ 根部拥有的引用模型用 `@State` 搭配 `@Observable` 类型;iOS 17+ 子视图读取或修改注入的 `@Observable` 模型时作为存储属性显式传入;共享应用服务或配置用 `@Environment(Type.self)`;iOS 16 及更早的遗留引用模型在根部用 `@StateObject`、注入时用 `@ObservedObject`。
- 先确定所有权位置,再选择属性包装器;纯值状态足够时不要引入引用模型。
- 按需阅读横切参考:`references/navigationstack.md`(导航所有权、每标签历史、枚举路由)、`references/sheets.md`(集中式模态呈现与枚举驱动 sheet)、`references/deeplinks.md`(URL 处理与外部链接路由)、`references/app-wiring.md`(根依赖图、environment 用法、应用外壳接线)、`references/async-state.md`(`.task`、`.task(id:)`、取消、防抖、异步 UI 状态)、`references/previews.md`(`#Preview`、fixtures、mock 环境、隔离预览)、`references/performance.md`(稳定标识、观察范围、惰性容器、渲染成本护栏)。
- 新建 SwiftUI 视图的工作流:1) 写 UI 代码前先定义视图状态、所有权位置和最低 OS 假设;2) 确定哪些依赖属于 `@Environment`、哪些应保留为显式初始化器输入;3) 勾画视图层级、路由模型和呈现点,把重复部分抽成子视图,复杂导航读 `references/navigationstack.md`、`references/sheets.md` 或 `references/deeplinks.md`,并在继续前构建验证无编译错误;4) 用 `.task` 或 `.task(id:)` 实现异步加载,必要时加显式 loading 和 error 状态,工作依赖变化输入或取消时读 `references/async-state.md`;5) 为主要和次要状态添加预览,UI 可交互时添加无障碍标签或标识符,视图需要 fixtures 或注入 mock 依赖时读 `references/previews.md`;6) 用构建验证:确认无编译错误、预览不崩溃、状态变化正确传播,并检查列表标识和观察范围不会造成可避免的重渲染,屏幕大、滚动多或频繁更新时读 `references/performance.md`;遇到常见 SwiftUI 编译错误(缺少 `@State` 注解、`ViewBuilder` 闭包歧义、泛型类型不匹配)先解决再更新调用点;构建失败时仔细读错误信息、修复问题后重新构建再继续;预览崩溃时隔离出问题的子视图、确认其状态初始化有效,再重新运行预览。
- 组件参考以 `references/components-index.md` 为入口,每个组件参考应包含:意图与最适用场景、符合本地约定的最小用法模式、陷阱与性能注意事项、当前仓库中现有示例的路径。
- 新增组件参考时:创建 `references/<component>.md`,保持简短可执行并链接到当前仓库的具体文件,然后更新 `references/components-index.md` 加入新条目。
适用边界 · 注意事项
- 不要写把布局、业务逻辑、网络、路由和格式化混在一个文件里的巨型视图。
- 不要用多个布尔标志表示互斥的 sheet、alert 或导航目标;也不要在 `body` 驱动的代码路径里直接做实时服务调用,而应放在视图生命周期钩子或注入的模型/服务中。
- 不要用 `AnyView` 绕过本应通过更好组合解决的类型不匹配,也不要无明确所有权理由就把所有共享依赖默认成 `@EnvironmentObject` 或全局 router。