# Swiftui Patterns

> When to activate: SwiftUI views, layouts, state management, view modifiers, navigation, lists, animations in SwiftUI

- Skill: `mattakushi432/swiftui-patterns` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/swiftui-patterns`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/swiftui-patterns/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/swiftui-patterns

---


# SwiftUI Patterns

## State Ownership

Use the right property wrapper for the right scope.

```swift
// @State — private, view-owned mutable state
struct CounterView: View {
    @State private var count = 0
    var body: some View {
        Button("Count: \(count)") { count += 1 }
    }
}

// @Binding — child receives reference to parent state
struct ToggleRow: View {
    let title: String
    @Binding var isOn: Bool
    var body: some View { Toggle(title, isOn: $isOn) }
}

// @StateObject — view creates and owns the ObservableObject
struct ProfileView: View {
    @StateObject private var vm = ProfileViewModel()
    var body: some View { Text(vm.name) }
}

// @ObservedObject — view receives (does NOT own) an ObservableObject
struct OrderRow: View {
    @ObservedObject var order: Order
    var body: some View { Text(order.status) }
}

// @EnvironmentObject — injected from ancestor; app-wide state
struct ThemeButton: View {
    @EnvironmentObject var theme: ThemeStore
    var body: some View { Button("Toggle") { theme.toggle() } }
}
```

## View Decomposition

Break large views into small, focused subviews. Use computed properties for static sub-trees.

```swift
struct ArticleView: View {
    let article: Article

    var body: some View {
        ScrollView {
            VStack(alignment: .leading, spacing: 16) {
                headerSection
                AuthorRow(author: article.author)
                Text(article.body).font(.body)
            }
            .padding()
        }
    }

    private var headerSection: some View {
        VStack(alignment: .leading, spacing: 8) {
            Text(article.title).font(.title.bold())
            Text(article.publishedAt, style: .date).foregroundStyle(.secondary)
        }
    }
}
```

## Navigation (NavigationStack)

Prefer typed `NavigationStack` paths over deprecated `NavigationView`.

```swift
enum Route: Hashable {
    case detail(Item)
    case settings
    case profile(User)
}

struct RootView: View {
    @State private var path: [Route] = []

    var body: some View {
        NavigationStack(path: $path) {
            ItemListView(path: $path)
                .navigationDestination(for: Route.self) { route in
                    switch route {
                    case .detail(let item):  ItemDetailView(item: item)
                    case .settings:          SettingsView()
                    case .profile(let user): ProfileView(user: user)
                    }
                }
        }
    }
}
```

## Lists and Lazy Stacks

```swift
List(items) { item in
    ItemRow(item: item)
        .swipeActions(edge: .trailing) {
            Button("Delete", role: .destructive) { delete(item) }
        }
}
.refreshable { await viewModel.reload() }

ScrollView {
    LazyVStack(spacing: 12, pinnedViews: .sectionHeaders) {
        ForEach(sections) { section in
            Section { ForEach(section.items) { ItemCard(item: $0) } }
                header: { SectionHeader(title: section.title) }
        }
    }
    .padding(.horizontal)
}
```

## Custom View Modifiers

```swift
struct CardStyle: ViewModifier {
    var cornerRadius: CGFloat = 12
    func body(content: Content) -> some View {
        content
            .padding()
            .background(.background, in: RoundedRectangle(cornerRadius: cornerRadius))
            .shadow(color: .black.opacity(0.08), radius: 8, y: 4)
    }
}

extension View {
    func card(cornerRadius: CGFloat = 12) -> some View {
        modifier(CardStyle(cornerRadius: cornerRadius))
    }
}
```

## Animations

```swift
Button("Expand") {
    withAnimation(.spring(response: 0.4, dampingFraction: 0.7)) {
        isExpanded.toggle()
    }
}

// Hero transitions
@Namespace private var heroNamespace
// Source view
Image(item.thumbnail).matchedGeometryEffect(id: item.id, in: heroNamespace)
// Destination view
Image(item.thumbnail).matchedGeometryEffect(id: item.id, in: heroNamespace)
```

## Common Anti-Patterns

- `@State` on reference types — use `@StateObject`
- `@ObservedObject` for view-created objects — they get recreated; use `@StateObject`
- `NavigationView` — deprecated; use `NavigationStack`
- Forcing `AnyView` everywhere — keep views typed with `some View`
- One massive `body` — decompose into subviews

