Help us improve
Share bugs, ideas, or general feedback.
From antigravity-awesome-skills
Refactors SwiftUI views into smaller subcomponents with stable, explicit data flow. Use for cleaning up large views, splitting long bodies, or improving Observation usage.
npx claudepluginhub sickn33/antigravity-awesome-skills --plugin antigravity-awesome-skillsHow this skill is triggered — by the user, by Claude, or both
Slash command
/antigravity-awesome-skills:swiftui-view-refactorThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Refactor SwiftUI views toward small, explicit, stable view types. Default to vanilla SwiftUI: local state in the view, shared dependencies in the environment, business logic in services/models, and view models only when the request or existing code clearly requires one.
Refactors large SwiftUI views into smaller component types with stable, explicit data flow. Prefers MV over MVVM, dedicated subview types, and explicit inputs.
Refactors SwiftUI views toward small dedicated subviews, MV over MVVM data flow, stable view trees, explicit dependency injection, and correct Observation usage. Use for cleaning up views, splitting long bodies, removing side effects, or standardizing @Observable patterns.
Refactors SwiftUI view files toward small subviews, MV-first data flow, stable view trees, explicit dependencies, extracted actions, and correct Observable usage.
Share bugs, ideas, or general feedback.
Refactor SwiftUI views toward small, explicit, stable view types. Default to vanilla SwiftUI: local state in the view, shared dependencies in the environment, business logic in services/models, and view models only when the request or existing code clearly requires one.
body implementations.private/public let@State / other stored propertiesvar (non-view)initbody@State, @Environment, @Query, .task, .task(id:), and onChange before reaching for a view model.@Environment; keep domain logic in services/models, not in the view body.some View helpersbody properties that are longer than roughly one screen or contain multiple logical sections.View types for non-trivial sections, especially when they have state, async work, branching, or deserve their own preview.some View helpers rare and small. Do not build an entire screen out of private var header: some View-style fragments.Prefer:
var body: some View {
List {
HeaderSection(title: title, subtitle: subtitle)
FilterSection(
filterOptions: filterOptions,
selectedFilter: $selectedFilter
)
ResultsSection(items: filteredItems)
FooterSection()
}
}
private struct HeaderSection: View {
let title: String
let subtitle: String
var body: some View {
VStack(alignment: .leading, spacing: 6) {
Text(title).font(.title2)
Text(subtitle).font(.subheadline)
}
}
}
private struct FilterSection: View {
let filterOptions: [FilterOption]
@Binding var selectedFilter: FilterOption
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(filterOptions, id: \.self) { option in
FilterChip(option: option, isSelected: option == selectedFilter)
.onTapGesture { selectedFilter = option }
}
}
}
}
}
Avoid:
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)
}
}
body.task, .onAppear, .onChange, or .refreshable.Button("Save", action: save)
.disabled(isSaving)
.task(id: searchText) {
await reload(for: searchText)
}
private func save() {
Task { await saveAsync() }
}
private func reload(for searchText: String) async {
guard !searchText.isEmpty else {
results = []
return
}
await searchService.search(searchText)
}
body or computed views that return completely different root branches via if/else.overlay, opacity, disabled, toolbar, etc.).Prefer:
var body: some View {
List {
documentsListContent
}
.toolbar {
if canEdit {
editToolbar
}
}
}
Avoid:
var documentsListView: some View {
if canEdit {
editableDocumentsList
} else {
readOnlyDocumentsList
}
}
init, then create the view model in the view's init.bootstrapIfNeeded patterns and other delayed setup workarounds.Example (Observation-based):
@State private var viewModel: SomeViewModel
init(dependency: Dependency) {
_viewModel = State(initialValue: SomeViewModel(dependency: dependency))
}
@Observable reference types on iOS 17+, store them as @State in the owning view.@StateObject at the owner and @ObservedObject when injecting legacy observable models.body; move business logic into services/models and keep only thin orchestration in the view.some View helpers.if-based branch swapping; move conditions to localized sections/modifiers.@State view model initialized in init.@State for root @Observable models on iOS 17+, legacy wrappers only when the deployment target requires them.some View properties.body and non-view computed vars above init.references/mv-patterns.md.When a SwiftUI view file exceeds ~300 lines, split it aggressively. Extract meaningful sections into dedicated View types instead of hiding complexity in many computed properties. Use private extensions with // MARK: - comments for actions and helpers, but do not treat extensions as a substitute for breaking a giant screen into smaller view types. If an extracted subview is reused or independently meaningful, move it into its own file.