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
Trigger phrases
Phrases that activate this skill when typed to Claude Code:
should I use @State or @Observable hereSwiftUI NavigationStack with type-safe routesfix 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 (
@Statevs@Observablevs@Binding) - Designing navigation flows with
NavigationStackand 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
- Name the state. “I have a list view that shows items, has a search field, and loads from a repository — what wrappers?”
- Skill walks the table.
searchTextis view-local string editing →@Bindinginto the view model’svar searchTextvia$viewModel.searchText.itemsandisLoadingare owned by the view model →@Observableclass withprivate(set)for the read-only fields. - Wire the view model.
@State private var viewModel: ItemListViewModelwith theinitthat uses_viewModel = State(initialValue:)to support custom initialization. @Observablefor the model class. No@Published. SwiftUI tracks property reads via the@Observablemacro and re-renders only the views that read the changed property..taskfor async load.await viewModel.load()runs once when the view appears.- Navigation.
NavigationStackwithnavigationDestination(for:)for type-safe routes — push aItem.IDonto the stack, the destination view receives it. - 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.