swiftui-view-refactor
SwiftUI View Refactor
Section titled “SwiftUI View Refactor”View Ordering (top to bottom)
Section titled “View Ordering (top to bottom)”- Environment properties
private/publicletconstants@State/ other stored properties- Computed
var(non-view) initbody- Computed view builders / view helpers
- Helper / async functions
Core Guidelines
Section titled “Core Guidelines”1) Prefer MV (Model-View) Patterns
Section titled “1) Prefer MV (Model-View) Patterns”- Default to MV: views are lightweight state expressions; models/services own business logic
- Favor
@State,@Environment,@Query,task, andonChangefor orchestration - Inject services and shared models via
@Environment - Split large views into smaller views instead of introducing a view model
2) Split Large Bodies
Section titled “2) Split Large Bodies”If body grows beyond a screen or has multiple logical sections, split it within this file:
var body: some View { VStack(alignment: .leading, spacing: 16) { HeaderSection(title: title, isPinned: isPinned) DetailsSection(details: details) ActionsSection(onSave: onSave, onCancel: onCancel) }}Or use computed view properties in the same file:
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 } } } }}3) View Model Handling (only if already present)
Section titled “3) 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 into the view model
@State private var viewModel: SomeViewModel
init(dependency: Dependency) { _viewModel = State(initialValue: SomeViewModel(dependency: dependency))}4) Observation Usage
Section titled “4) Observation Usage”- For
@Observablereference types, store them as@Statein the root view - Pass observables down explicitly as needed
- Avoid optional state unless required
Refactor Workflow
Section titled “Refactor Workflow”- Reorder the view to match the ordering rules
- Favor MV: move orchestration into the view using
@State,@Environment,task,onChange - If a view model exists, replace optional with non-optional
@Stateinitialized ininit - Confirm Observation usage:
@Statefor root@Observable, no redundant wrappers - Keep behavior intact: do not change layout or business logic unless requested
Large-View Handling
Section titled “Large-View Handling”When a SwiftUI view file exceeds ~300 lines:
- Split using extensions to group related helpers
- Move async functions into dedicated
privateextensions - Use
// MARK: -comments (e.g.,// MARK: - Actions,// MARK: - Subviews) - Keep main
structfocused on stored properties, init, andbody
struct LargeView: View { @Environment(Store.self) private var store @State private var items: [Item] = []
var body: some View { List { content } .task { await loadItems() } }}
// MARK: - Subviewsprivate extension LargeView { var content: some View { ForEach(items) { item in ItemRow(item: item) } }}
// MARK: - Actionsprivate extension LargeView { func loadItems() async { items = await store.fetchItems() }}Keep extracted sections in this file. Once a section needs its own @ViewBuilder API, container/composition logic, or reuse across files, move it out and hand off to the swiftui-expert-skill skill.
Checklist
Section titled “Checklist”- Properties ordered correctly (Environment → let → @State → computed → init → body)
- Large body split into subviews or computed view properties
- No unnecessary ViewModels introduced
- Existing ViewModels are non-optional where possible
-
@Observabletypes stored as@Statein root view - Dependencies injected via
@Environment - File organized with MARK comments if >300 lines
- Behavior unchanged unless explicitly requested