关于 ui-patterns
面向 SwiftUI 开发者的界面模式技能,指导在现有或新项目中组织视图、状态和导航。强调小型组合视图、SwiftUI 原生数据流,以及由视图管理的 Sheet 操作。
主要功能
- 01规划视图状态与归属
- 02组合小型、职责明确的视图
- 03使用 SwiftUI 状态与环境注入
- 04处理异步加载和错误状态
- 05采用项目现有代码风格
适用场景
- 01为新功能构建列表、详情或编辑界面
- 02为新项目搭建标签页与导航
- 03统一 Sheet 和状态管理模式
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ patrickserrano/ skills --skill ui-patterns安装后在 Claude Code 中输入/ui-patterns确认已加载。
详细介绍
SwiftUI UI 模式
快速入门
现有项目
- 确定功能和主要交互模式(列表、详情、编辑器、设置、标签页)
- 使用
rg "TabView\("或类似命令,在仓库中查找相近示例 - 遵循本地约定:优先使用 SwiftUI 原生状态,并将状态保留在本地
- 使用小型、专注的子视图构建,并采用 SwiftUI 原生数据流
新项目脚手架
- 配置 TabView + NavigationStack + sheets
- 添加最简的
AppTab枚举和RouterPath - 随着添加新屏幕,扩展路由和 sheet 枚举
通用规则
- 使用现代 SwiftUI 状态(
@State、@Binding、@Observable、@Environment) - 避免不必要的视图模型——优先采用 MV(Model-View),而非 MVVM
- 优先使用组合;让视图保持精简且专注
- 使用
.task配合 async/await,并明确处理加载和错误状态 - 遵循项目的格式化工具和风格指南
Sheet 最佳实践
- 当状态表示选中的模型时,优先使用
.sheet(item:),而非.sheet(isPresented:) - 避免在 sheet 主体中使用
if let - Sheet 应负责自身的操作,并在内部调用
dismiss()
新建 SwiftUI 视图的工作流
- 定义视图的状态及其所有权位置
- 确定通过
@Environment注入的依赖项 - 勾勒视图层级,并将重复部分提取为子视图
- 使用
.task实现异步加载;必要时使用明确的状态枚举 - 为交互式 UI 添加无障碍标签或标识符
- 通过构建进行验证,并在需要时更新使用位置
MV 模式(优先于 MVVM)
SwiftUI 视图应是轻量的状态表达式。除非确有必要,否则避免使用 ViewModel。
struct FeedView: View {
@Environment(APIClient.self) private var client
enum ViewState {
case loading
case error(String)
case loaded([Post])
}
@State private var viewState: ViewState = .loading
var body: some View {
NavigationStack {
List {
switch viewState {
case .loading:
ProgressView("Loading...")
case .error(let message):
ErrorView(message: message, retry: { await loadFeed() })
case .loaded(let posts):
ForEach(posts) { post in
PostRow(post: post)
}
}
}
.task { await loadFeed() }
}
}
private func loadFeed() async {
do {
let posts = try await client.getFeed()
viewState = .loaded(posts)
} catch {
viewState = .error(error.localizedDescription)
}
}
}
Sheet 模式
由项目驱动的 sheet(推荐)
@State private var selectedItem: Item?
.sheet(item: $selectedItem) { item in
EditItemSheet(item: item)
}
由 Sheet 负责自身操作
struct EditItemSheet: View {
@Environment(\.dismiss) private var dismiss
@Environment(Store.self) private var store
let item: Item
@State private var isSaving = false
var body: some View {
VStack {
Button(isSaving ? "Saving..." : "Save") {
Task { await save() }
}
}
}
private func save() async {
isSaving = true
await store.save(item)
dismiss()
}
}
应用级环境设置
@main
struct MyApp: App {
@State var client: APIClient = .init()
@State var router: AppRouter = .init()
var body: some Scene {
WindowGroup {
TabView(selection: $router.selectedTab) {
ForEach(AppTab.allCases) { tab in
tab.rootView
.tag(tab)
}
}
.environment(client)
.environment(router)
}
}
}
状态管理指南
| 包装器 | 使用场景 |
|---|---|
@State | 本地、临时的视图状态 |
@Binding | 来自父视图的双向数据流 |
@Observable | 跨视图共享状态(iOS 17+) |
@Environment | 依赖注入、应用级关注点 |
@Query | 在视图中直接执行 SwiftData 查询 |
Task 和 onChange 模式
// React to state changes
.task(id: searchText) {
guard !searchText.isEmpty else { return }
await search(query: searchText)
}
// Respond to state transitions
.onChange(of: isActive, initial: false) {
guard isActive else { return }
Task { await refresh() }
}
为什么不使用 MVVM?
SwiftUI 的设计初衷并不包括 ViewModel:
- 视图是结构体,轻量且可随时丢弃
@State、@Environment、@Observable可满足所有数据流需求- ViewModel 会增加复杂性、间接层和认知负担
- SwiftData 的
@Query可直接在视图中使用
建议改为:
- 让视图成为状态的纯粹表达
- 将业务逻辑放入通过
@Environment注入的服务/模型中 - 测试服务和模型,而非视图
- 使用 SwiftUI Previews 进行视觉回归测试
评论
支持 Markdown