dpearson2699/swift-ios-skills8 files

Swiftui Patterns

Builds and reviews SwiftUI views with modern MV architecture, state, composition, isolated previews, and migration guidance. Covers @Observable ownership, @State/@Bindable/@Environment wiring, view decomposition, ViewModifiers, environment values, .task loading, iOS 26+ handoffs, Writing Tools, clipboard availability, and performance. Use when structuring SwiftUI state, managing @Observable, composing views, previewing meaningful UI states, or correcting SwiftUI patterns.

Specification
Skill ID
dpearson2699/swift-ios-skills/swiftui-patterns
Publisher
dpearson2699
Repository
swift-ios-skills
Installs
0
Files
8
Synced
Sep 23, 2026
How to use it

Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.

dpearson2699/swift-ios-skills/swiftui-patternsInstalls these files
  • SKILL.md
  • evals/evals.json
  • references/architecture-patterns.md
  • references/deprecated-migration.md
  • references/design-polish.md
  • references/platform-and-sharing.md
  • references/preview-isolation.md
  • references/view-refactoring.md

What this skill tells the agent

SwiftUI Patterns

Modern SwiftUI patterns targeting iOS 26+ with Swift 6.3. Covers architecture, state management, view composition, environment wiring, async loading, design polish, and platform/share integration. Navigation, layout, animation, and Liquid Glass patterns live in dedicated sibling skills. Patterns are backward-compatible to iOS 17 unless noted.

Contents

  • Architecture: Model-View (MV) Pattern
  • Workflow
  • State Management
  • View Ordering Convention
  • View Composition
  • Environment
  • Async Data Loading
  • iOS 26+ New APIs
  • Performance Guidelines
  • HIG Alignment
  • Writing Tools (iOS 18+)
  • Common Mistakes
  • Review Checklist
  • References

Scope boundary: This skill covers architecture, state ownership, composition, environment wiring, async loading, and related SwiftUI app structure patterns. Detailed navigation patterns are covered in the swiftui-navigation skill, including NavigationStack, NavigationSplitView, sheets, tabs, and deep-linking patterns. Detailed layout, container, and component patterns are covered in the swiftui-layout-components skill, including stacks, grids, lists, scroll view patterns, forms, controls, search UI with .searchable, overlays, and related layout components. Detailed animation choreography is covered in swiftui-animation. Liquid Glass adoption, custom glass controls, scroll edge effects, .scrollEdgeEffectStyle, and .backgroundExtensionEffect are covered in swiftui-liquid-glass.

Workflow

  1. Record the current state ownership, actions, side effects, navigation, and lifecycle behavior.
  2. Choose the smallest MV/state/composition change that preserves that contract.
  3. Build after each structural step; fix compiler and isolation errors before continuing.
  4. Render deterministic previews for loaded, loading, empty, and error states as applicable, including required environment dependencies.
  5. Exercise important interactions and side effects. If behavior changes, restore the fixture, fix the smallest boundary, and rerun the same build, preview, and interaction checks.

Load Behavior-Preserving View Refactoring for restructuring existing views and Isolated Preview Construction for fixture and dependency patterns.

Architecture: Model-View (MV) Pattern

Default to MV -- views are lightweight state expressions; models and services own business logic. Do not introduce view models unless the existing code already uses them.

Core principles:

  • Favor @State, @Environment, @Query, .task, and .onChange for orchestration
  • Inject services and shared models via @Environment; keep views small and composable
  • Split large views into smaller subviews rather than introducing a view model
  • Test models, services, and business logic; keep views simple and declarative
struct FeedView: View {
    @Environment(FeedClient.self) private var client

    enum ViewState {
        case loading, error(String), loaded([Post])
    }

    @State private var viewState: ViewState = .loading

    var body: some View {
        List {
            switch viewState {
            case .loading:
                ProgressView()
            case .error(let message):
                ContentUnavailableView("Error", systemImage: "exclamationmark.triangle",
                                       description: Text(message))
            case .loaded(let posts):
                ForEach(posts) { post in
                    PostRow(post: post)
                }
            }
        }
        .task { await loadFeed() }
        .refreshable { await loadFeed() }
    }

    private func loadFeed() async {
        do {
            let posts = try await client.getFeed()
            viewState = .loaded(posts)
        } catch {
            viewState = .error(error.localizedDescription)
        }
    }
}

For MV pattern rationale, app wiring, and lightweight client examples, see references/architecture-patterns.md.

State Management

@Observable Ownership Rules

Important: Isolate UI-bound @Observable stores and view models on @MainActor when SwiftUI views own them, mutate them, or bind to their properties. Observation tracks changes; it does not make shared mutable state thread-safe. Domain models that do not touch UI state can use their own isolation strategy.

WrapperWhen to Use
@StateView owns the object or value. Creates and manages lifecycle.
letView receives an @Observable object. Read-only observation -- no wrapper needed.
@BindableView receives an @Observable object and needs two-way bindings ($property).
@Environment(Type.self)Access shared @Observable object from environment.
@State (value types)View-local simple state: toggles, counters, text field values. Always private.
@BindingTwo-way connection to parent's @State or @Bindable property.

Ownership Pattern

// UI-bound @Observable store -- main-actor isolated
@MainActor
@Observable final class ItemStore {
    var title = ""
    var items: [Item] = []
}

// View that OWNS the model
struct ParentView: View {
    @State private var viewModel = ItemStore()

    var body: some View {
        ChildView(store: viewModel)
            .environment(viewModel)
    }
}

// View that READS (no wrapper needed for @Observable)
struct ChildView: View {
    let store: ItemStore

    var body: some View { Text(store.title) }
}

// View that BINDS (needs two-way access)
struct EditView: View {
    @Bindable var store: ItemStore

    var body: some View {
        TextField("Title", text: $store.title)
    }
}

// View that reads from ENVIRONMENT
struct DeepView: View {
    @Environment(ItemStore.self) private var store

    var body: some View {
        @Bindable var s = store
        TextField("Title", text: $s.title)
    }
}

Granular tracking: SwiftUI only re-renders views that read properties that changed. If a view reads items but not isLoading, changing isLoading does not trigger a re-render. This is a major performance advantage over ObservableObject.

Legacy ObservableObject

Only use if supporting iOS 16 or earlier. @StateObject → @State, @ObservedObject → let, @EnvironmentObject → @Environment(Type.self).

View Ordering Convention

Order members top to bottom: 1) @Environment 2) let properties 3) @State / stored properties 4) computed var 5) init 6) body 7) view builders / helpers 8) async functions

View Composition

Extract Subviews

Break views into focused subviews. Each should have a single responsibility. When restructuring an existing view, load Behavior-Preserving View Refactoring for action/side-effect boundaries and build/preview proof.

var body: some View {
    VStack {
        HeaderSection(title: title, isPinned: isPinned)
        DetailsSection(details: details)
        ActionsSection(onSave: onSave, onCancel: onCancel)
    }
}

Computed View Properties

Keep computed some View properties for small, stateless fragments. Extract a section into a dedicated View type when it has any of these signals:

  • meaningful branching or substantial layout
  • its own state or async lifecycle
  • narrower Observation dependencies than the parent
  • a useful independent preview
  • enough complexity to obscure the parent's data flow

Use this skill in a project

Open any RiverX project, search the Skills panel for dpearson2699/swift-ios-skills/swiftui-patterns, and install it in one click.

Use this skill