Refactor SwiftUI Views for Clarity and Maintainability
A skill that refactors large SwiftUI views into small, stable dedicated subview types with an MV-first, not MVVM, approach.
Why it matters
Improve the structure and readability of your SwiftUI codebase by refactoring complex views into smaller, more manageable components. This skill promotes best practices for state management, dependency injection, and view composition.
Outcomes
What it gets done
Split large SwiftUI views into dedicated subviews.
Extract inline actions and side effects out of view bodies.
Ensure stable view trees by avoiding top-level conditional view swapping.
Apply MV (Model-View) patterns over MVVM where appropriate.
Install
Add it to your toolbox
Run in your project directory:
curl -fsSL https://spark.entire.vc/get/ag-swiftui-view-refactor | bash Overview
SwiftUI View Refactor
A skill that refactors large SwiftUI views into small, stable, dedicated subview types using an MV-first approach - correct property ordering, extracted subviews, stable view identity, and proper Observation usage. Use it when cleaning up a large or long-bodied SwiftUI view; it changes structure only and does not alter existing layout or business logic unless explicitly requested.
What it does
This skill refactors SwiftUI views toward small, explicit, stable view types, defaulting to plain SwiftUI (MV) - local state in the view, shared dependencies via environment, business logic in services/models - reaching for a view model only when the request or existing code clearly requires one. It enforces a top-to-bottom property ordering within a view file (environment, private/public let, @State/stored properties, computed non-view vars, init, body, computed view builders, helper/async functions), preserving a stronger existing local convention if one is already in place. It defaults to MV over MVVM: views should be lightweight state expressions and orchestration points using @State, @Environment, @Query, .task, and onChange, with domain logic living in injected services/models rather than the view body, and never introducing a view model just to mirror local state or wrap environment dependencies - splitting UI into subviews comes before inventing a view-model layer. It strongly prefers dedicated View struct types over computed some View helper properties for any non-trivial section (especially ones with state, async work, branching, or that deserve their own preview), passing small explicit inputs (data, bindings, callbacks) rather than the entire parent state, and promoting genuinely reusable subviews to their own file - with a worked before/after example replacing private var header: some View fragments with dedicated HeaderSection/FilterSection view structs. It extracts actions and side effects out of body entirely, keeping button actions and .task/.onAppear/.onChange/.refreshable closures thin by delegating to small private methods that call into services, so body reads like UI rather than a view controller. It keeps the view tree stable by avoiding root-level if/else branch swapping (which causes identity churn and broad invalidation) in favor of a single stable base view with conditions localized to sections or modifiers like .toolbar/.overlay/.disabled. When a view model does exist or is explicitly requested, it makes it non-optional where possible, created in the view's init and passed dependencies there rather than using bootstrapIfNeeded-style delayed setup. For Observation usage, @Observable reference types on iOS 17+ are stored as @State in the owning view and passed down explicitly (avoiding optional state unless genuinely needed), falling back to @StateObject/@ObservedObject only for iOS 16 and earlier. The workflow runs seven steps: reorder the view, remove inline body actions/side effects, extract dedicated subview types to shorten long bodies, stabilize the view structure by removing top-level conditional branching, replace optional view models with non-optional @State ones if applicable, confirm correct Observation usage, and keep behavior/layout/business-logic unchanged unless explicitly requested. For files exceeding roughly 300 lines it recommends aggressive splitting into dedicated view types (using // MARK: - extensions for actions/helpers, not as a substitute for real decomposition) and moving independently meaningful subviews into their own files.
When to use - and when NOT to
Use it when cleaning up a large SwiftUI view or splitting a long body implementation, or when smaller subviews, explicit dependency injection, or better Observation usage are needed. It does not change layout or business logic unless explicitly requested - the refactor targets structure, not behavior.
Inputs and outputs
Inputs: an existing SwiftUI view file (typically large or with a long body) that needs structural refactoring.
Outputs: the same view decomposed into ordered properties, dedicated subview types replacing computed helper properties, extracted action/side-effect methods, a stable non-branching view tree, and correct Observation-based state ownership - with behavior unchanged.
var body: some View {
List {
HeaderSection(title: title, subtitle: subtitle)
FilterSection(filterOptions: filterOptions, selectedFilter: $selectedFilter)
ResultsSection(items: filteredItems)
}
}
Who it's for
iOS/macOS developers cleaning up large or unwieldy SwiftUI views who want a consistent, MV-first structural refactor - dedicated subviews, thin bodies, stable identity - without changing existing behavior.
FAQ
Common questions
Discussion
Questions & comments ยท 0
Sign In Sign in to leave a comment.