Guide Skill — This is an expert workflow/pattern guide, not API reference documentation. Originally from Dimillian/Skills by Thomas Ricouard. MIT License.
SwiftUI View Refactor
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.
Core Guidelines
1) View ordering (top → bottom)
- •Environment
- •
private/publiclet - •
@State/ other stored properties - •computed
var(non-view) - •
init - •
body - •computed view builders / other view helpers
- •helper / async functions
2) Prefer MV (Model-View) patterns
- •Default to MV: Views are lightweight state expressions; models/services own business logic.
- •Favor
@State,@Environment,@Query, andtask/onChangefor orchestration. - •Inject services and shared models via
@Environment; keep views small and composable. - •Split large views into subviews rather than introducing a view model.
3) Split large bodies and view properties
- •If
bodygrows beyond a screen or has multiple logical sections, split it into smaller subviews. - •Extract large computed view properties (
var header: some View { ... }) into dedicatedViewtypes 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
Viewstruct 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)
}
}
}
3b) Keep a stable view tree (avoid top-level conditional view swapping)
- •Avoid patterns where a computed view (or
body) returns completely different root branches usingif/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
}
}
4) View model handling (only if already present)
- •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'sinit. - •Avoid
bootstrapIfNeededpatterns.
Example (Observation-based):
swift
@State private var viewModel: SomeViewModel
init(dependency: Dependency) {
_viewModel = State(initialValue: SomeViewModel(dependency: dependency))
}
5) Observation usage
- •For
@Observablereference types, store them as@Statein the root view. - •Pass observables down explicitly as needed; avoid optional state unless required.
Workflow
- •Reorder the view to match the ordering rules.
- •Favor MV: move lightweight orchestration into the view using
@State,@Environment,@Query,task, andonChange. - •Ensure stable view structure: avoid top-level
if-based branch swapping; move conditions to localized sections/modifiers. - •If a view model exists, replace optional view models with a non-optional
@Stateview model initialized ininitby passing dependencies from the view. - •Confirm Observation usage:
@Statefor root@Observableview models, no redundant wrappers. - •Keep behavior intact: do not change layout or business logic unless requested.
Notes
- •Prefer small, explicit helpers over large conditional blocks.
- •Keep computed view builders below
bodyand non-view computed vars aboveinit. - •For MV-first guidance and rationale, see
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
privateextensions, separated with// MARK: -comments that describe their purpose (e.g.,// MARK: - Actions,// MARK: - Subviews,// MARK: - Helpers). Keep the mainstructfocused on stored properties, init, andbody, with view-building computed vars also grouped via marks when the file is long.