guide-swiftui-view-refactor

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
Guide Skill — This is an expert workflow/pattern guide, not API reference documentation. Originally from Dimillian/Skills by Thomas Ricouard. MIT License.
指南技能 — 这是一份专家级工作流/模式指南,而非API参考文档。 最初由Thomas Ricouard发布于 Dimillian/Skills,采用MIT许可证。

SwiftUI View Refactor

SwiftUI 视图重构

Overview

概述

Apply a consistent structure and dependency pattern to SwiftUI views, with a focus on ordering, Model-View (MV) patterns, careful view model handling, and correct Observation usage.
为SwiftUI视图应用统一的结构和依赖模式,重点关注排序规则、模型-视图(MV)模式、严谨的视图模型处理以及正确的Observation使用方式。

Core Guidelines

核心准则

1) View ordering (top → bottom)

1) 视图代码排序(从上到下)

  • Environment
  • private
    /
    public
    let
  • @State
    / other stored properties
  • computed
    var
    (non-view)
  • init
  • body
  • computed view builders / other view helpers
  • helper / async functions
  • Environment
  • private
    /
    public
    let
    常量
  • @State
    / 其他存储属性
  • 非视图类计算属性
    var
  • init
    构造方法
  • body
  • 计算视图构建器 / 其他视图辅助方法
  • 辅助方法 / 异步函数

2) Prefer MV (Model-View) patterns

2) 优先使用MV(模型-视图)模式

  • Default to MV: Views are lightweight state expressions; models/services own business logic.
  • Favor
    @State
    ,
    @Environment
    ,
    @Query
    , and
    task
    /
    onChange
    for orchestration.
  • Inject services and shared models via
    @Environment
    ; keep views small and composable.
  • Split large views into subviews rather than introducing a view model.
  • 默认采用MV模式:视图是轻量的状态表达式,业务逻辑由模型/服务持有。
  • 优先使用
    @State
    @Environment
    @Query
    以及
    task
    /
    onChange
    来编排逻辑。
  • 通过
    @Environment
    注入服务和共享模型,保持视图小巧且可组合。
  • 将大型视图拆分为子视图,而非引入视图模型。

3) Split large bodies and view properties

3) 拆分冗长的body和视图属性

  • If
    body
    grows beyond a screen or has multiple logical sections, split it into smaller subviews.
  • Extract large computed view properties (
    var header: some View { ... }
    ) into dedicated
    View
    types when they carry state or complex branching.
  • It's fine to keep related subviews as computed view properties in the same file; extract to a standalone
    View
    struct only when it structurally makes sense or when reuse is intended.
  • Prefer passing small inputs (data, bindings, callbacks) over reusing the entire parent view state.
Example (extracting a section):
swift
var body: some View {
    VStack(alignment: .leading, spacing: 16) {
        HeaderSection(title: title, isPinned: isPinned)
        DetailsSection(details: details)
        ActionsSection(onSave: onSave, onCancel: onCancel)
    }
}
Example (long body → shorter body + computed views in the same file):
swift
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)
    }
}

private var filters: some View {
    ScrollView(.horizontal, showsIndicators: false) {
        HStack {
            ForEach(filterOptions, id: \.self) { option in
                FilterChip(option: option, isSelected: option == selectedFilter)
                    .onTapGesture { selectedFilter = option }
            }
        }
    }
}
Example (extracting a complex computed view):
swift
private var header: some View {
    HeaderSection(title: title, subtitle: subtitle, status: status)
}

private struct HeaderSection: View {
    let title: String
    let subtitle: String?
    let status: Status

    var body: some View {
        VStack(alignment: .leading, spacing: 4) {
            Text(title).font(.headline)
            if let subtitle { Text(subtitle).font(.subheadline) }
            StatusBadge(status: status)
        }
    }
}
  • 如果
    body
    的代码长度超过一屏,或是包含多个逻辑区块,将其拆分为更小的子视图。
  • 当大型计算视图属性(
    var header: some View { ... }
    )携带状态或包含复杂分支时,将其提取为独立的
    View
    类型。
  • 可以将相关子视图作为计算视图属性保留在同一文件中;仅当结构上合理或需要复用时,才将其提取为独立的
    View
    结构体。
  • 优先传递小范围输入(数据、绑定、回调),而非复用整个父视图的状态。
示例(提取某个区块):
swift
var body: some View {
    VStack(alignment: .leading, spacing: 16) {
        HeaderSection(title: title, isPinned: isPinned)
        DetailsSection(details: details)
        ActionsSection(onSave: onSave, onCancel: onCancel)
    }
}
示例(长body → 短body + 同文件内的计算视图):
swift
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)
    }
}

private var filters: some View {
    ScrollView(.horizontal, showsIndicators: false) {
        HStack {
            ForEach(filterOptions, id: \.self) { option in
                FilterChip(option: option, isSelected: option == selectedFilter)
                    .onTapGesture { selectedFilter = option }
            }
        }
    }
}
示例(提取复杂计算视图):
swift
private var header: some View {
    HeaderSection(title: title, subtitle: subtitle, status: status)
}

private struct HeaderSection: View {
    let title: String
    let subtitle: String?
    let status: Status

    var body: some View {
        VStack(alignment: .leading, spacing: 4) {
            Text(title).font(.headline)
            if let subtitle { Text(subtitle).font(.subheadline) }
            StatusBadge(status: status)
        }
    }
}

3b) Keep a stable view tree (avoid top-level conditional view swapping)

3b) 保持视图树稳定(避免顶层条件视图切换)

  • Avoid patterns where a computed view (or
    body
    ) returns completely different root branches using
    if/else
    .
  • Prefer a single stable base view, and place conditions inside sections/modifiers (
    overlay
    ,
    opacity
    ,
    disabled
    ,
    toolbar
    , row content, etc.).
  • Root-level branch swapping can cause identity churn, broader invalidation, and extra recomputation in SwiftUI.
Prefer:
swift
var body: some View {
    List {
        documentsListContent
    }
    .toolbar {
        if canEdit {
            editToolbar
        }
    }
}
Avoid:
swift
var documentsListView: some View {
    if canEdit {
        editableDocumentsList
    } else {
        readOnlyDocumentsList
    }
}
  • 避免在计算视图(或
    body
    )中使用
    if/else
    返回完全不同的根分支的模式。
  • 优先使用单一稳定的基础视图,将条件逻辑放在区块/修饰符内部(
    overlay
    opacity
    disabled
    toolbar
    、行内容等)。
  • 根层级的分支切换会导致标识 churn、大范围失效,以及SwiftUI额外的重计算开销。
推荐写法:
swift
var body: some View {
    List {
        documentsListContent
    }
    .toolbar {
        if canEdit {
            editToolbar
        }
    }
}
不推荐写法:
swift
var documentsListView: some View {
    if canEdit {
        editableDocumentsList
    } else {
        readOnlyDocumentsList
    }
}

4) View model handling (only if already present)

4) 视图模型处理(仅当已存在时)

  • Do not introduce a view model unless the request or existing code clearly calls for one.
  • If a view model exists, make it non-optional when possible.
  • Pass dependencies to the view via
    init
    , then pass them into the view model in the view's
    init
    .
  • Avoid
    bootstrapIfNeeded
    patterns.
Example (Observation-based):
swift
@State private var viewModel: SomeViewModel

init(dependency: Dependency) {
    _viewModel = State(initialValue: SomeViewModel(dependency: dependency))
}
  • 除非需求或现有代码明确要求,否则不要引入视图模型。
  • 如果已存在视图模型,尽可能将其设为非可选类型。
  • 通过
    init
    将依赖项传递给视图,然后在视图的
    init
    中将其传递给视图模型。
  • 避免使用
    bootstrapIfNeeded
    这类模式。
示例(基于Observation的实现):
swift
@State private var viewModel: SomeViewModel

init(dependency: Dependency) {
    _viewModel = State(initialValue: SomeViewModel(dependency: dependency))
}

5) Observation usage

5) Observation使用规范

  • For
    @Observable
    reference types, store them as
    @State
    in the root view.
  • Pass observables down explicitly as needed; avoid optional state unless required.
  • 对于
    @Observable
    引用类型,在根视图中以
    @State
    的形式存储。
  • 根据需要显式向下传递可观测对象;除非必要,否则避免使用可选状态。

Workflow

工作流

  1. Reorder the view to match the ordering rules.
  2. Favor MV: move lightweight orchestration into the view using
    @State
    ,
    @Environment
    ,
    @Query
    ,
    task
    , and
    onChange
    .
  3. Ensure stable view structure: avoid top-level
    if
    -based branch swapping; move conditions to localized sections/modifiers.
  4. If a view model exists, replace optional view models with a non-optional
    @State
    view model initialized in
    init
    by passing dependencies from the view.
  5. Confirm Observation usage:
    @State
    for root
    @Observable
    view models, no redundant wrappers.
  6. Keep behavior intact: do not change layout or business logic unless requested.
  1. 按照排序规则重排视图代码。
  2. 优先采用MV模式:使用
    @State
    @Environment
    @Query
    task
    onChange
    将轻量级编排逻辑放在视图内部。
  3. 确保视图结构稳定:避免顶层基于
    if
    的分支切换;将条件逻辑移到局部区块/修饰符中。
  4. 如果存在视图模型,将可选视图模型替换为非可选的
    @State
    视图模型,在
    init
    中通过传递给视图的依赖项完成初始化。
  5. 确认Observation使用规范:根层级
    @Observable
    视图模型使用
    @State
    包装,无冗余包装器。
  6. 保持行为不变:除非有明确要求,否则不要修改布局或业务逻辑。

Notes

注意事项

  • Prefer small, explicit helpers over large conditional blocks.
  • Keep computed view builders below
    body
    and non-view computed vars above
    init
    .
  • For MV-first guidance and rationale, see
    references/mv-patterns.md
    .
  • 优先使用小型、明确的辅助方法,而非大型条件块。
  • 计算视图构建器放在
    body
    下方,非视图类计算属性放在
    init
    上方。
  • 如需了解MV优先的指导和设计原理,请参考
    references/mv-patterns.md

Large-view handling

大型视图处理

  • When a SwiftUI view file exceeds ~300 lines, split it using extensions to group related helpers. Move async functions and helper functions into dedicated
    private
    extensions, separated with
    // MARK: -
    comments that describe their purpose (e.g.,
    // MARK: - Actions
    ,
    // MARK: - Subviews
    ,
    // MARK: - Helpers
    ). Keep the main
    struct
    focused on stored properties, init, and
    body
    , with view-building computed vars also grouped via marks when the file is long.
  • 当SwiftUI视图文件超过约300行时,使用扩展对相关辅助方法进行分组。将异步函数和辅助方法移到专用的
    private
    扩展中,使用描述其用途的
    // MARK: -
    注释分隔(例如
    // MARK: - 操作方法
    // MARK: - 子视图
    // MARK: - 辅助工具
    )。当文件较长时,保持主
    struct
    聚焦于存储属性、构造方法和
    body
    ,视图构建计算属性也通过标记进行分组。