swiftui-view-refactor
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSwiftUI 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视图应用一致的结构和依赖模式,重点关注代码顺序、Model-View(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
- /
privatepubliclet - / 其他存储属性
@State - 计算型(非视图相关)
var initbody- 计算型视图构建器 / 其他视图辅助方法
- 辅助函数 / 异步函数
2) Prefer MV (Model-View) patterns
2) 优先使用MV(Model-View)模式
- 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) 拆分大型视图主体和视图属性
- 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)
}
}示例(长主体 → 短主体 + 同一文件中的计算型视图):
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 - 根层级的分支切换会导致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 - 如果视图模型已存在,将可选视图模型替换为在中通过视图传递依赖项初始化的非可选
init视图模型。@State - 确认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