domains / software-engineering / swiftui-view-refactor / swiftui-view-refactor

swiftui-view-refactor

Skill card

combined

The three copies share the same core MV-first, subview-extraction, and Observation guidance, but they disagree on how aggressively to extract subviews and on the scope of the refactor. Dimillian/Skills and sickn33/agentic-awesome-skills strongly prefer dedicated View types over computed some View helpers and include an explicit rule to extract actions and side effects out of body. LeoYeAI/openclaw-master-skills explicitly allows keeping related subviews as computed view properties in the same file and omits the side-effect extraction rule. Because crowning one copy would hide a real alternative approach to subview extraction, the copies should be combined.

Repositories

RepositoryPath
Dimillian/Skillsswiftui-view-refactor/SKILL.md
sickn33/agentic-awesome-skillsplugins/agentic-awesome-skills-claude/skills/swiftui-view-refactor/SKILL.md
LeoYeAI/openclaw-master-skillsskills/swiftui-view-refactor/SKILL.md

Other copies

RoleRepositoryPath
peerDimillian/Skillsswiftui-view-refactor/SKILL.md
peersickn33/agentic-awesome-skillsplugins/agentic-awesome-skills-claude/skills/swiftui-view-refactor/SKILL.md
peerLeoYeAI/openclaw-master-skillsskills/swiftui-view-refactor/SKILL.md
adaptersickn33/agentic-awesome-skillsplugins/agentic-awesome-skills/skills/swiftui-view-refactor/SKILL.md
adaptersickn33/agentic-awesome-skillsskills/swiftui-view-refactor/SKILL.md

Combined skill

---
name: swiftui-view-refactor
description: Refactor and review SwiftUI view files with strong defaults for small dedicated subviews, MV-over-MVVM data flow, stable view trees, explicit dependency injection, and correct Observation usage. Use when cleaning up a SwiftUI view, splitting long bodies, removing inline actions or side effects, reducing computed some View helpers, or standardizing @Observable and view model initialization patterns.
---

# SwiftUI View Refactor

## Overview
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.

## When to Use
- When cleaning up a large SwiftUI view or splitting long body implementations.
- When you need smaller subviews, explicit dependency injection, or better Observation usage.
- When asked to clean up a SwiftUI view's layout/ordering, handle view models safely, or standardize how dependencies and @Observable state are initialized and passed.

## Core Guidelines

### 1) View ordering (top → bottom)
- Enforce this ordering unless the existing file has a stronger local convention you must preserve.
- Environment
- private/public let
- @State / other stored properties
- computed var (non-view)
- init
- body
- computed view builders / other view helpers
- helper / async functions

### 2) Default to MV, not MVVM
- Views should be lightweight state expressions and orchestration points, not containers for business logic.
- Favor @State, @Environment, @Query, .task, .task(id:), and onChange before reaching for a view model.
- Inject services and shared models via @Environment; keep domain logic in services/models, not in the view body.
- Do not introduce a view model just to mirror local view state or wrap environment dependencies.
- If a screen is getting large, split the UI into subviews before inventing a new view model layer.

### 3) Prefer dedicated subview types over computed some View helpers
- Flag body properties that are longer than roughly one screen or contain multiple logical sections.
- Prefer extracting dedicated View types for non-trivial sections, especially when they have state, async work, branching, or deserve their own preview.
- Keep computed some View helpers rare and small. Do not build an entire screen out of private var header: some View-style fragments.
- Pass small, explicit inputs (data, bindings, callbacks) into extracted subviews instead of handing down the entire parent state.
- If an extracted subview becomes reusable or independently meaningful, move it to its own file.

Prefer:

```swift
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:

```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)
    }
}
```

### 3b) Extract actions and side effects out of body
- Do not keep non-trivial button actions inline in the view body.
- Do not bury business logic inside .task, .onAppear, .onChange, or .refreshable.
- Prefer calling small private methods from the view, and move real business logic into services/models.
- The body should read like UI, not like a view controller.

```swift
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)
}
```

### 4) Keep a stable view tree (avoid top-level conditional view swapping)
- Avoid body or computed views that return completely different root branches via if/else.
- Prefer a single stable base view with conditions inside sections/modifiers (overlay, opacity, disabled, toolbar, etc.).
- Root-level branch swapping causes identity churn, broader invalidation, and extra recomputation.

Prefer:

```swift
var body: some View {
    List {
        documentsListContent
    }
    .toolbar {
        if canEdit {
            editToolbar
        }
    }
}
```

Avoid:

```swift
var documentsListView: some View {
    if canEdit {
        editableDocumentsList
    } else {
        readOnlyDocumentsList
    }
}
```

### 5) View model handling (only if already present or explicitly requested)
- Treat view models as a legacy or explicit-need pattern, not the default.
- 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 create the view model in the view's init.
- Avoid bootstrapIfNeeded patterns and other delayed setup workarounds.

Example (Observation-based):

```swift
@State private var viewModel: SomeViewModel

init(dependency: Dependency) {
    _viewModel = State(initialValue: SomeViewModel(dependency: dependency))
}
```

### 6) Observation usage
- For @Observable reference types on iOS 17+, store them as @State in the owning view.
- Pass observables down explicitly; avoid optional state unless required.

## Workflow

1) Reorder the view to match the ordering rules.
2) Favor MV: move lightweight orchestration into the view using @State, @Environment, @Query, task, and onChange.
3) If a view model exists, replace optional view models with a non-optional @State view model initialized in init by passing dependencies from the view.
4) Confirm Observation usage: @State for root @Observable view models, no redundant wrappers.
5) 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 body and non-view computed vars above init.
- For MV-first guidance and rationale, see references/mv-patterns.md.