Skip to main content

swiftui-patterns

A Claude Code skill from Affaan M's everything-claude-code repo for modern SwiftUI architecture — @Observable state management with property-level tracking, property-wrapper selection table (@State / @Binding / @Bindable / @Environment), type-safe NavigationStack flows, environment injection via @Environment(Type.self), and rendering-performance patterns for lists and complex layouts.

Pick the right SwiftUI property wrapper and view-composition pattern instead of defaulting to ObservableObject everywhere

Source Affaan M
License MIT
First documented
Receipts TODO

Trigger phrases

Phrases that activate this skill when typed to Claude Code:

  • should I use @State or @Observable here
  • SwiftUI NavigationStack with type-safe routes
  • fix re-render performance on this list

What it does

swiftui-patterns is the SwiftUI architecture skill in Affaan M’s everything-claude-code — see skills/swiftui-patterns. It’s the composition / state / navigation reference for declarative iOS / macOS UI — distinct from liquid-glass-design (which is about the iOS 26 material) and swift-concurrency-6-2 (which is about actor isolation).

The property-wrapper selection table is the operational core: @State for view-local value types (toggles, form fields, sheet presentation), @Binding for two-way reference to parent’s @State, @Observable class + @State for owned models with multiple properties, plain @Observable reference for read-only injection, @Bindable for two-way bindings into @Observable properties, @Environment for shared dependencies injected via .environment(). The shift from ObservableObject to @Observable is the modern default — @Observable tracks property-level changes so SwiftUI only re-renders views that actually read the changed property.

The skill covers NavigationStack patterns for type-safe navigation, view-composition rules (extract subviews to limit invalidation scope), @Environment(Type.self) as the modern replacement for @EnvironmentObject, and performance patterns for lists and complex layouts. View-model integration is shown explicitly: @State private var viewModel: ItemListViewModel with an init that initializes via _viewModel = State(initialValue: viewModel) — the modern shape rather than @StateObject.

When to use it

  • Building SwiftUI views and choosing the right state-management primitive (@State vs @Observable vs @Binding)
  • Designing navigation flows with NavigationStack and type-safe destinations
  • Structuring view models and data flow with @Observable
  • Optimizing rendering performance for lists and complex layouts (extracting subviews to limit invalidation)
  • Dependency injection via @Environment(Type.self) rather than @EnvironmentObject

When not to reach for it:

  • Liquid Glass material application — that’s liquid-glass-design
  • Actor / concurrency / data-race work — that’s swift-concurrency-6-2
  • On-device LLM features — that’s foundation-models-on-device
  • UIKit-only code — the skill is SwiftUI-shaped

Install

From affaan-m/everything-claude-code at skills/swiftui-patterns/. Drop the folder into ~/.claude/skills/swiftui-patterns/. The skill is markdown + Swift code templates; the runtime is Xcode 26+ targeting iOS 17+ for the @Observable macro (older deployment targets are stuck with ObservableObject).

What a session looks like

  1. Name the state. “I have a list view that shows items, has a search field, and loads from a repository — what wrappers?”
  2. Skill walks the table. searchText is view-local string editing → @Binding into the view model’s var searchText via $viewModel.searchText. items and isLoading are owned by the view model → @Observable class with private(set) for the read-only fields.
  3. Wire the view model. @State private var viewModel: ItemListViewModel with the init that uses _viewModel = State(initialValue:) to support custom initialization.
  4. @Observable for the model class. No @Published. SwiftUI tracks property reads via the @Observable macro and re-renders only the views that read the changed property.
  5. .task for async load. await viewModel.load() runs once when the view appears.
  6. Navigation. NavigationStack with navigationDestination(for:) for type-safe routes — push a Item.ID onto the stack, the destination view receives it.
  7. Environment injection. .environment(authManager) from the root, @Environment(AuthManager.self) in the consumer.

The discipline that makes it work: pick the simplest wrapper that fits. The selection table runs in increasing complexity — @State first, only escalate to @Observable when there are multiple related properties, only inject via @Environment when the dependency is genuinely shared.

Receipts

TODO — to be filled in from a real session. Once the patterns have been used to build a real SwiftUI screen, this section will capture: how often @Observable + property-level tracking actually narrowed the re-render scope vs. the operator’s prior ObservableObject baseline, which property wrapper choice was wrong on first pass and needed swapping (the most common is reaching for @Environment when @Bindable would do), whether the NavigationStack type-safe routes held up against deep-link / restoration requirements, and the actual frame-time on a complex list before vs. after extracting subviews to limit invalidation.

Source and attribution

From Affaan M’s everything-claude-code — an MIT-licensed skill collection covering harness construction, agent ops, video, payments, and platform-specific patterns.

License: MIT.