guide-swiftui-view-refactor
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGuide 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
- /
privatepubliclet - / other stored properties
@State - computed (non-view)
var initbody- computed view builders / other view helpers
- helper / async functions
- Environment
- /
privatepublic常量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, and@Query/taskfor orchestration.onChange - Inject services and shared models via ; keep views small and composable.
@Environment - 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 grows beyond a screen or has multiple logical sections, split it into smaller subviews.
body - Extract large computed view properties () into dedicated
var header: some View { ... }types when they carry state or complex branching.View - It's fine to keep related subviews as computed view properties in the same file; extract to a standalone struct only when it structurally makes sense or when reuse is intended.
View - 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 ) returns completely different root branches using
body.if/else - Prefer a single stable base view, and place conditions inside sections/modifiers (,
overlay,opacity,disabled, row content, etc.).toolbar - 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 , then pass them into the view model in the view's
init.init - Avoid patterns.
bootstrapIfNeeded
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 reference types, store them as
@Observablein the root view.@State - Pass observables down explicitly as needed; avoid optional state unless required.
- 对于引用类型,在根视图中以
@Observable的形式存储。@State - 根据需要显式向下传递可观测对象;除非必要,否则避免使用可选状态。
Workflow
工作流
- Reorder the view to match the ordering rules.
- Favor MV: move lightweight orchestration into the view using ,
@State,@Environment,@Query, andtask.onChange - Ensure stable view structure: avoid top-level -based branch swapping; move conditions to localized sections/modifiers.
if - If a view model exists, replace optional view models with a non-optional view model initialized in
@Stateby passing dependencies from the view.init - Confirm Observation usage: for root
@Stateview models, no redundant wrappers.@Observable - Keep behavior intact: do not change layout or business logic unless requested.
- 按照排序规则重排视图代码。
- 优先采用MV模式:使用、
@State、@Environment、@Query和task将轻量级编排逻辑放在视图内部。onChange - 确保视图结构稳定:避免顶层基于的分支切换;将条件逻辑移到局部区块/修饰符中。
if - 如果存在视图模型,将可选视图模型替换为非可选的视图模型,在
@State中通过传递给视图的依赖项完成初始化。init - 确认Observation使用规范:根层级视图模型使用
@Observable包装,无冗余包装器。@State - 保持行为不变:除非有明确要求,否则不要修改布局或业务逻辑。
Notes
注意事项
- Prefer small, explicit helpers over large conditional blocks.
- Keep computed view builders below and non-view computed vars above
body.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 extensions, separated with
privatecomments that describe their purpose (e.g.,// MARK: -,// MARK: - Actions,// MARK: - Subviews). Keep the main// MARK: - Helpersfocused on stored properties, init, andstruct, with view-building computed vars also grouped via marks when the file is long.body
- 当SwiftUI视图文件超过约300行时,使用扩展对相关辅助方法进行分组。将异步函数和辅助方法移到专用的扩展中,使用描述其用途的
private注释分隔(例如// MARK: -、// MARK: - 操作方法、// MARK: - 子视图)。当文件较长时,保持主// MARK: - 辅助工具聚焦于存储属性、构造方法和struct,视图构建计算属性也通过标记进行分组。body