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, clip
By dpearson2699 · 3,933 installs
npx skills add dpearson2699/swift-ios-skills --skill swiftui-patterns
Source repository · Upstream listing
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]( architecture model view mv pattern)
[Workflow]( workflow)
[State Management]( state management)
[View Ordering Convention]( view ordering convention)
[View Composition]( view composition)
[Environment]( environment)
[Async Data Loading]( async data loading)
[iOS 26+ New APIs]( ios 26 new apis)
[Performance Guidelines]( performance guidelines)
[HIG Alignment]( hig alignment)
[Writing Tools (iOS 18+)]( writing tools ios 18)
[Common Mistakes]( common mistakes)
[Review Checklist]( review checklist)
[References]( 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](references/view refactoring.md) for restructuring existing views and [Isolated Preview Construction](references/preview isolation.md) 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
For MV pattern rationale, app wiring, and lightweight client examples, see [references/architecture patterns.md](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.
Wrapper When to Use
@State View owns the object or value. Creates and manages lifecycle.
let View receives an @Observable object. Read only observation no wrapper needed.
@Bindable View 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 .
@Binding Two way connection to parent's @State or @Bindable property.
Ownership Pattern
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](references/view refactoring.md)
for action/side effect boundaries and build/preview proof.
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
When narrowing dependencies, pass only the values, bindings, and actions the child needs. If they form a large but cohesive interface, pass a feature scoped @Observable model. Observation limits invalidation to properties the child reads, but an app wide store still creates a broad interface; reserve it for children that genuinely need that cohesive state.
Reuse is a useful outcome, not a prerequisite for decomposition.
Extensions and // MARK: organize a large file; they do not create view boundaries or replace extraction.
ViewBuilder Functions
For conditional logic that does not warrant a separate struct:
Custom View Modifiers
Extract repeated styling into ViewModifier :
Stable View Tree
Avoid top level conditional view swapping. Prefer a single stable base view with conditions inside sections or modifiers.
When extracted views need independent state coverage, deterministic fixtures, or environment setup, load [Isolated Preview Construction](references/preview isolation.md).
Environment
Custom Environment Values
Use @Entry for custom environment values and actions. It generates the entry boilerplate for EnvironmentValues .
For iOS 17 compatible code or older compatibility shims, use manual EnvironmentKey types instead.
Common Built in Environment Values
Async Data Loading
Always use .task it cancels automatically on view disappear:
Use .task(id:) to re run when a dependency changes:
Never create manual Task in onAppear unless you need to store a reference for cancellation. Exception: Task {} is acceptable in synchronous action closures (e.g., Button actions) for immediate state updates before async work.
Use swift concurrency for cancellation handlers, debounce and clocks, AsyncSequence , or actor isolation.
iOS 26+ New APIs
Route .scrollEdgeEffectStyle , .backgroundExtensionEffect , and glass controls to swiftui liquid glass ; route @Animatable to swiftui animation . TextEditor(text: Binding<AttributedString ) is the iOS 26 rich text editing path. Keep availability checks beside code that adopts these APIs.
Clipboard command modifiers are not iOS 26 defaults: .copyable , .cuttable , and command based .pasteDestination(for:action:validator:) are macOS 13+ and iOS/iPadOS/Mac Catalyst 27 beta in current Apple docs. For iOS 26 targets, use UIPasteboard for custom clipboard commands, or use drag/drop and ShareLink for Transferable flows. See [references/platform and sharing.md](references/platform and sharing.md).
Performance Guidelines
Lazy stacks/grids: Use LazyVStack , LazyHStack , LazyVGrid , LazyHGrid for large collections. Regular stacks render all children immediately.
Stable IDs: All items in List / ForEach must conform to Identifiable with stable IDs. Never use array indices.
Avoid body recomputation: Move filtering and sorting to computed properties or the model, not inline in body .
Equatable views: For complex views that re render unnecessarily, conform to Equatable .
HIG Alignment
Follow Apple Human Interface Guidelines for layout, typography, color, and accessibility. Key rules:
Use semantic colors ( Color.primary , .secondary , Color(uiColor: .systemBackground) ) for automatic light/dark mode
Use system font styles ( .title , .headline , .body , .caption ) for Dynamic Type support
Use ContentUnavailableView for empty and error states
Omit spacing: on stacks unless a specific value is required — nil (the default) uses platform appropriate adaptive spacing
Support adaptive layouts via horizontalSizeClass
Provide VoiceOver labels ( .accessibilityLabel ) and support Dynamic Type accessibility sizes by switching layout orientation
See [references/design polish.md](references/design polish.md) for HIG, theming, haptics, focus, transitions, and loading patterns.
Writing Tools (iOS 18+)
Control the Apple Intelligence Writing Tools experience on text views with .writingToolsBehavior( :) .
Level Effect When to use
.complete Full inline rewriting (proofread, rewrite, transform) Notes, email, documents
.limited Reduced overlay panel experience Code editors, validated forms
.disabled Writing Tools hidden entirely Passwords, search bars
.automatic System chooses based on context (default) Most views
Detecting active sessions: Read isWritingToolsActive on UITextView (UIKit) to defer validation or suspend undo grouping until a rewrite finishes.
Docs: [WritingToolsBehavior](https://sosumi.ai/documentation/swiftui/writingtoolsbehavior) · [writingToolsBehavior( :)](https://sosumi.ai/documentation/swiftui/view/writingtoolsbehavior( :))
Common Mistakes
1. Using @ObservedObject to create objects use @StateObject (legacy) or @State (modern)
2. Heavy computation in view body move to model or computed property
3. Not using .task for async work manual Task in onAppear leaks if not cancelled
4. Array indices as ForEach IDs causes incorrect diffing and UI bugs
5. Forgetting @Bindable $property syntax on @Observable requires @Bindable
6. Over using @State only for view local state; shared state belongs in @Observable
7. Keeping complex or independently previewable sections computed extract View types; extensions and // MARK: only organize
8. Using NavigationView deprecated; use NavigationStack
9. Reaching for foregroundColor( :) when foregroundStyle( :) better matches semantic styling
10. Inline closures in body extract complex closures to methods
11. .sheet(isPresented:) when state represents a model use .sheet(item:) instead
12. Using AnyView for routine branching type erasure hides structure and can hurt performance or identity sensitive transitions. Use @ViewBuilder , Group , or generics unless an API genuinely needs heterogeneous view storage. See [references/deprecated migration.md](references/deprecated migration.md)
13. Putting @AppStorage inside an @Observable class. @AppStorage is a view DynamicProperty ; keep it in a View , or expose a normal observed property backed by UserDefaults in the model.
14. Hard coding spacing: on every stack omit it to get adaptive platform spacing; only specify when the value is intentional
15. Treating .copyable , .cuttable , o