mcpField
G
guide-swiftui-view-refactor

guide-swiftui-view-refactor

github.com/prisma-labs-dev开发349193 次安装5 周前更新
代码仓库

关于 guide-swiftui-view-refactor

面向处理 SwiftUI 代码的编码 Agent,提供将视图重构为小型、明确类型的工作流,并将业务逻辑留在视图之外。指南优先采用原生 SwiftUI 状态模式,默认不新增 view model。

主要功能

  1. 01视图属性排序规则
  2. 02优先 MV 的状态与依赖指南
  3. 03大型视图拆分为子视图
  4. 04将操作和副作用移出 body
  5. 05稳定视图树与 Observation 模式

适用场景

  1. 01重构较长的 SwiftUI 屏幕
  2. 02将内联按钮逻辑移到方法或服务中
  3. 03用稳定布局替代条件式根视图

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/prisma-labs-dev/apple-skills --skill guide-swiftui-view-refactor

安装后在 Claude Code 中输入/guide-swiftui-view-refactor确认已加载。

附带文件references/mv-patterns.md

详细介绍

Guide Skill —— 这是专家工作流/模式指南,而非 API 参考文档。 最初来自 Thomas Ricouard 的 Dimillian/Skills。MIT License。

SwiftUI 视图重构

概述

将 SwiftUI 视图重构为小巧、明确且稳定的视图类型。默认使用原生 SwiftUI:在视图中管理局部状态,通过环境共享依赖,将业务逻辑放在服务/模型中,只有在请求或现有代码明确要求时才使用视图模型。

核心指南

1) 视图顺序(从上到下)
  • 除非现有文件有必须保留的、更强的局部约定,否则请遵循以下顺序。
  • Environment
  • private/public let
  • @State/其他存储属性
  • 计算属性 var(非视图)
  • init
  • body
  • 计算视图构建器/其他视图辅助项
  • 辅助函数/异步函数
2) 默认采用 MV,而非 MVVM
  • 视图应是轻量的状态表达和编排点,而不是业务逻辑的容器。
  • 优先考虑 @State、@Environment、@Query、.task、.task(id:) 和 onChange,再考虑使用视图模型。
  • 通过 @Environment 注入服务和共享模型;将领域逻辑放在服务/模型中,而不是视图主体中。
  • 不要仅为映射局部视图状态或封装环境依赖而引入视图模型。
  • 如果界面变得庞大,先拆分子视图,再考虑创建新的视图模型层。
3) 强烈建议使用专用子视图类型,而非计算属性 some View 辅助项
  • 关注长度超过大约一个屏幕或包含多个逻辑区块的 body 属性。
  • 对于非平凡的区块,优先提取专用的 View 类型;尤其是它们包含状态、异步工作、分支逻辑,或适合拥有自己的预览时。
  • 尽量少用计算属性 some View 辅助项,并保持简短。不要用 private var header: some View 之类的片段拼出整个界面。
  • 将小而明确的输入(数据、绑定、回调)传入提取出的子视图,而不是传递整个父视图状态。
  • 如果提取出的子视图变得可复用或具有独立意义,请将其移到单独的文件中。

优先使用:

var body: some View {
    List {
        HeaderSection(title: title, subtitle: subtitle)
        FilterSection(
            filterOptions: filterOptions,
            selectedFilter: $selectedFilter
        )
        ResultsSection(items: filteredItems)
        FooterSection()
    }
}

private struct HeaderSection: View {
    let title: String
    let subtitle: String

    var body: some View {
        VStack(alignment: .leading, spacing: 6) {
            Text(title).font(.title2)
            Text(subtitle).font(.subheadline)
        }
    }
}

private struct FilterSection: View {
    let filterOptions: [FilterOption]
    @Binding var selectedFilter: FilterOption

    var body: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            HStack {
                ForEach(filterOptions, id: \.self) { option in
                    FilterChip(option: option, isSelected: option == selectedFilter)
                        .onTapGesture { selectedFilter = option }
                }
            }
        }
    }
}

避免使用:

var body: some View {
    List {
        header
        filters
        results
        footer
    }
}

private var header: some View {
    VStack(alignment: .leading, spacing: 6) {
        Text(title).font(.title2)
        Text(subtitle).font(.subheadline)
    }
}
3b) 将操作和副作用从 body 中提取出来
  • 不要将非平凡的按钮操作内联写在视图主体中。
  • 不要把业务逻辑塞进 .task、.onAppear、.onChange 或 .refreshable 中。
  • 优先从视图调用简短的私有方法,并将真正的业务逻辑移入服务/模型。
  • body 应读起来像 UI,而不是视图控制器。
Button("Save", action: save)
    .disabled(isSaving)

.task(id: searchText) {
    await reload(for: searchText)
}

private func save() {
    Task { await saveAsync() }
}

private func reload(for searchText: String) async {
    guard !searchText.isEmpty else {
        results = []
        return
    }
    await searchService.search(searchText)
}
4) 保持视图树稳定(避免顶层条件视图切换)
  • 避免让 body 或计算视图通过 if/else 返回完全不同的根分支。
  • 优先使用单一且稳定的基础视图,在区块/修饰符(overlay、opacity、disabled、toolbar 等)中添加条件。
  • 根级分支切换会导致身份频繁变化、扩大失效范围并增加重复计算。

优先使用:

var body: some View {
    List {
        documentsListContent
    }
    .toolbar {
        if canEdit {
            editToolbar
        }
    }
}

避免使用:

var documentsListView: some View {
    if canEdit {
        editableDocumentsList
    } else {
        readOnlyDocumentsList
    }
}
5) 视图模型处理(仅在已存在或明确要求时)
  • 将视图模型视为旧有模式或满足明确需求时采用的模式,而非默认选择。
  • 除非请求或现有代码明确要求,否则不要引入视图模型。
  • 如果存在视图模型,尽可能将其设为非可选。
  • 通过 init 将依赖传入视图,然后在视图的 init 中创建视图模型。
  • 避免使用 bootstrapIfNeeded 模式和其他延迟设置变通方案。

示例(基于 Observation):

@State private var viewModel: SomeViewModel

init(dependency: Dependency) {
    _viewModel = State(initialValue: SomeViewModel(dependency: dependency))
}
6) Observation 的使用
  • 对于 iOS 17+ 上的 @Observable 引用类型,在所属视图中使用 @State 存储。
  • 显式向下传递可观察对象;除非 UI 确实需要,否则避免使用可选状态。
  • 如果最低部署目标包含 iOS 16 或更早版本,请在所属视图中使用 @StateObject,并在注入旧式可观察模型时使用 @ObservedObject。

工作流

  1. 重新排列视图,使其符合排序规则。
  2. 从 body 中移除内联操作和副作用;将业务逻辑移入服务或模型,并让视图仅保留精简的编排逻辑。
  3. 通过提取专用子视图类型来缩短冗长的 body;避免用许多计算属性 some View 帮助器重构屏幕。
  4. 确保视图结构稳定:避免在顶层通过 if 切换分支;将条件移到局部区域或修饰符中。
  5. 如果存在视图模型或明确要求使用视图模型,请将可选视图模型替换为非可选的 @State 视图模型,并在 init 中初始化。
  6. 确认 Observation 的用法:在 iOS 17 及以上版本中,对根 @Observable 模型使用 @State;仅当部署目标要求时才使用旧版包装器。
  7. 保持行为不变:除非有明确要求,否则不要更改布局或业务逻辑。

说明

  • 优先使用小型、明确的视图类型,而非大型条件代码块和大型计算属性 some View。
  • 将计算视图构建器放在 body 之后,将非视图计算变量放在 init 之前。
  • 优秀的 SwiftUI 重构应让视图从上到下呈现数据流和布局,而不是混杂布局与命令式逻辑。
  • 关于 MV 优先的指导和理由,请参阅 references/mv-patterns.md。

大型视图处理

当 SwiftUI 视图文件超过约 300 行时,应积极拆分。将有意义的区域提取为专用的 View 类型,而不是用许多计算属性隐藏复杂性。对操作和辅助方法使用带有 // MARK: - 注释的 private 扩展,但不要把扩展当作将大型屏幕拆分为较小视图类型的替代方案。如果提取出的子视图会被复用或本身具有独立意义,请将其移到单独的文件中。

评论

支持 Markdown