SwiftUI Implementation Patterns
Use this skill to keep SwiftUI changes composable, state-safe, and aligned with iOS 17+, MVVM, the new Observation APIs, and Factory for dependency injection.
Delivery Workflow
Use the checklist below and track progress:
SwiftUI progress:
- [ ] Step 1: Discover project conventions and navigation model
- [ ] Step 2: Map screen state, ViewModel ownership, and Factory dependencies
- [ ] Step 3: Compose views into small reusable units
- [ ] Step 4: Wire Observation-based bindings and async work safely
- [ ] Step 5: Verify accessibility, previews, and tests
Platform Assumptions
- Target iOS 17+ by default.
- Prefer the modern Observation model over legacy
ObservableObject patterns unless the project still depends on older code paths.
- Do not add compatibility fallbacks for pre-iOS 17 APIs unless the repository explicitly requires them.
- Apple's
swiftui-specialist guidance supersedes model memory on framework behavior; when it conflicts with this skill on framework-level correctness (identity, invalidation, environment, deprecations), follow it and keep this skill for project conventions (MVVM, Factory).
- On SDK 27+,
@State is a macro: an @State build error after an SDK update is not fixed by reordering init assignments — see swiftui-whats-new-27.
- Use
NavigationStack/NavigationSplitView, not the soft-deprecated NavigationView, and the two-parameter/zero-parameter onChange.
State Management Rules
- Keep
View types focused on presentation and lightweight interaction wiring.
- Prefer one clear source of truth for screen state.
- Treat the
ViewModel as the owner of screen business state in MVVM flows.
- Use the project's existing observation model first; do not mix competing patterns without a reason.
- Keep transient UI state local to the view and move shared/business state into an observable
ViewModel.
- Derive display values from state instead of duplicating them.
Property Wrapper Guidance
| Use case |
Preferred approach |
| View-local ephemeral state |
@State |
| Child edits parent-owned state |
@Binding |
| View binds into an observable model |
@Bindable where binding into Observation-backed state is needed |
| View owns lifecycle of observable model |
Instantiate or inject the MVVM ViewModel using the project-standard ownership pattern |
| View receives externally owned observable model |
Keep ownership upstream and pass the model intentionally |
| Global shared dependencies |
Factory-backed injection or existing environment pattern only when already established |
MVVM Boundaries
- Keep SwiftUI
View types declarative and thin.
- Put business decisions, loading orchestration, and mutation logic in the
ViewModel.
- Keep the
ViewModel API intention-revealing: inputs as methods/actions, outputs as observable state.
- Avoid turning the
ViewModel into a generic service container; inject explicit dependencies instead.
Observation API Guidance
- Prefer
@Observable models for screen state on iOS 17+ codepaths.
- Use
@Bindable when the view needs direct bindings into observable model properties.
- Keep observation granular enough that the screen updates predictably.
- Do not mix legacy Combine-style observation and new Observation patterns inside one feature unless the surrounding architecture forces it.
Factory Dependency Injection
- Use
Factory as the default DI mechanism when resolving services, repositories, or coordinators.
- Resolve dependencies at clear boundaries, typically in composition roots or ViewModel initialization, not ad hoc throughout the view tree.
- Keep
Factory registrations aligned with feature boundaries and environment needs.
- Prefer injecting protocol-shaped collaborators into the
ViewModel over resolving dependencies repeatedly inside methods.
View Composition
- Split large screens into focused subviews before a single file becomes hard to scan.
- Extract reusable rows, sections, and controls when they have a stable API.
- Keep modifiers close to the element they affect unless a reusable style abstraction already exists.
- Avoid deep nesting when a helper view or computed subview will make structure clearer.
- Prefer data-driven rendering for repeated content.
Navigation and Presentation
- Follow the app's existing navigation model exactly.
- Keep navigation intent close to the state that controls it.
- Model sheets, alerts, confirmation dialogs, and destination routing explicitly instead of using scattered booleans.
- Reset temporary presentation state when dismissal should clear stale data.
Async Work and Side Effects
- Keep side effects out of the
View where practical; route them through the ViewModel.
- Isolate loading, refresh, and mutation states explicitly.
- Prevent duplicate requests from repeated lifecycle triggers.
- Keep async tasks cancellable when the screen can disappear.
- Update UI state on the main actor when required by the project pattern.
- Surface loading, empty, success, and error states intentionally.
Performance and Rendering
- Avoid expensive derived computations inside
body.
- Prefer stable identity in
ForEach and list rendering.
- Limit unnecessary re-renders caused by broad shared state.
- Do not hide architectural problems behind ad hoc memoization.
Accessibility and UX
- Provide clear labels, values, and hints for interactive elements.
- Preserve logical focus and reading order.
- Ensure tap targets are comfortably usable.
- Support dynamic type and long localized strings.
- Do not encode meaning with color alone.
SwiftUI Review Checklist
Structure:
- [ ] View hierarchy is readable and decomposed appropriately
- [ ] State ownership is clear and non-duplicated
- [ ] Navigation and presentation state are explicit
Behavior:
- [ ] Loading, error, empty, and success states are handled
- [ ] Async work is not triggered redundantly
- [ ] User actions cannot leave stale state behind
Quality:
- [ ] MVVM boundaries remain clear
- [ ] Observation API usage is consistent
- [ ] Factory-based dependencies are resolved intentionally
- [ ] Accessibility is covered
- [ ] Previews or equivalent fast feedback paths exist where appropriate
- [ ] Tests cover critical state transitions and presentation logic
Anti-Patterns to Avoid
| Anti-Pattern |
Instead Do |
| One giant screen view |
Split into subviews with clear responsibilities |
| Duplicate booleans for the same presentation state |
Use one explicit routing/presentation model |
Business logic in body |
Move logic into the ViewModel or focused helpers |
| Hidden side effects in view builders |
Trigger side effects intentionally and visibly |
| Legacy observation added to new iOS 17-only features without reason |
Use @Observable and @Bindable consistently |
| Resolving dependencies all over the feature |
Inject collaborators cleanly with Factory and clear ownership |
| New app-wide state pattern for one feature |
Reuse the project's existing pattern |
Connected Skills
swiftui-specialist - Apple best practices for view structure, data flow, @Environment/@Entry, ForEach identity, animations, and soft-deprecated APIs
swiftui-whats-new-27 - new SDK 27 SwiftUI APIs and @State/@ContentBuilder migration errors
building-document-based-swiftui-applications - document-based apps
swift-localization - use when the task involves multilingual strings, String Catalogs, or locale-aware UI behavior
technical-context-discovery - follow project iOS conventions before editing
frontend-implementation - apply UI quality and accessibility rules
ui-verification - verify screen output against approved designs when relevant
1---2name: swiftui3description: SwiftUI implementation and review patterns for iOS 17+ Apple-platform UI using MVVM, the Observation API, and Factory-based dependency injection. Use when Codex needs to build, refactor, debug, or review SwiftUI views, navigation flows, state management, forms, lists, async presentation logic, or dependency wiring in an MVVM SwiftUI codebase.4metadata:5 source: https://skillmd.com/skills/jakubmikolajek/swiftui6---78# SwiftUI Implementation Patterns910Use this skill to keep SwiftUI changes composable, state-safe, and aligned with iOS 17+, MVVM, the new Observation APIs, and `Factory` for dependency injection.1112## Delivery Workflow1314Use the checklist below and track progress:1516```17SwiftUI progress:18- [ ] Step 1: Discover project conventions and navigation model19- [ ] Step 2: Map screen state, ViewModel ownership, and Factory dependencies20- [ ] Step 3: Compose views into small reusable units21- [ ] Step 4: Wire Observation-based bindings and async work safely22- [ ] Step 5: Verify accessibility, previews, and tests23```2425## Platform Assumptions2627- Target iOS 17+ by default.28- Prefer the modern Observation model over legacy `ObservableObject` patterns unless the project still depends on older code paths.29- Do not add compatibility fallbacks for pre-iOS 17 APIs unless the repository explicitly requires them.30- Apple's `swiftui-specialist` guidance supersedes model memory on framework behavior; when it conflicts with this skill on framework-level correctness (identity, invalidation, environment, deprecations), follow it and keep this skill for project conventions (MVVM, Factory).31- On SDK 27+, `@State` is a macro: an `@State` build error after an SDK update is not fixed by reordering init assignments — see `swiftui-whats-new-27`.32- Use `NavigationStack`/`NavigationSplitView`, not the soft-deprecated `NavigationView`, and the two-parameter/zero-parameter `onChange`.3334## State Management Rules3536- Keep `View` types focused on presentation and lightweight interaction wiring.37- Prefer one clear source of truth for screen state.38- Treat the `ViewModel` as the owner of screen business state in MVVM flows.39- Use the project's existing observation model first; do not mix competing patterns without a reason.40- Keep transient UI state local to the view and move shared/business state into an observable `ViewModel`.41- Derive display values from state instead of duplicating them.4243### Property Wrapper Guidance4445| Use case | Preferred approach |46|---|---|47| View-local ephemeral state | `@State` |48| Child edits parent-owned state | `@Binding` |49| View binds into an observable model | `@Bindable` where binding into Observation-backed state is needed |50| View owns lifecycle of observable model | Instantiate or inject the MVVM `ViewModel` using the project-standard ownership pattern |51| View receives externally owned observable model | Keep ownership upstream and pass the model intentionally |52| Global shared dependencies | `Factory`-backed injection or existing environment pattern only when already established |5354## MVVM Boundaries5556- Keep SwiftUI `View` types declarative and thin.57- Put business decisions, loading orchestration, and mutation logic in the `ViewModel`.58- Keep the `ViewModel` API intention-revealing: inputs as methods/actions, outputs as observable state.59- Avoid turning the `ViewModel` into a generic service container; inject explicit dependencies instead.6061## Observation API Guidance6263- Prefer `@Observable` models for screen state on iOS 17+ codepaths.64- Use `@Bindable` when the view needs direct bindings into observable model properties.65- Keep observation granular enough that the screen updates predictably.66- Do not mix legacy Combine-style observation and new Observation patterns inside one feature unless the surrounding architecture forces it.6768## Factory Dependency Injection6970- Use `Factory` as the default DI mechanism when resolving services, repositories, or coordinators.71- Resolve dependencies at clear boundaries, typically in composition roots or ViewModel initialization, not ad hoc throughout the view tree.72- Keep `Factory` registrations aligned with feature boundaries and environment needs.73- Prefer injecting protocol-shaped collaborators into the `ViewModel` over resolving dependencies repeatedly inside methods.7475## View Composition7677- Split large screens into focused subviews before a single file becomes hard to scan.78- Extract reusable rows, sections, and controls when they have a stable API.79- Keep modifiers close to the element they affect unless a reusable style abstraction already exists.80- Avoid deep nesting when a helper view or computed subview will make structure clearer.81- Prefer data-driven rendering for repeated content.8283## Navigation and Presentation8485- Follow the app's existing navigation model exactly.86- Keep navigation intent close to the state that controls it.87- Model sheets, alerts, confirmation dialogs, and destination routing explicitly instead of using scattered booleans.88- Reset temporary presentation state when dismissal should clear stale data.8990## Async Work and Side Effects9192- Keep side effects out of the `View` where practical; route them through the `ViewModel`.93- Isolate loading, refresh, and mutation states explicitly.94- Prevent duplicate requests from repeated lifecycle triggers.95- Keep async tasks cancellable when the screen can disappear.96- Update UI state on the main actor when required by the project pattern.97- Surface loading, empty, success, and error states intentionally.9899## Performance and Rendering100101- Avoid expensive derived computations inside `body`.102- Prefer stable identity in `ForEach` and list rendering.103- Limit unnecessary re-renders caused by broad shared state.104- Do not hide architectural problems behind ad hoc memoization.105106## Accessibility and UX107108- Provide clear labels, values, and hints for interactive elements.109- Preserve logical focus and reading order.110- Ensure tap targets are comfortably usable.111- Support dynamic type and long localized strings.112- Do not encode meaning with color alone.113114## SwiftUI Review Checklist115116```117Structure:118- [ ] View hierarchy is readable and decomposed appropriately119- [ ] State ownership is clear and non-duplicated120- [ ] Navigation and presentation state are explicit121122Behavior:123- [ ] Loading, error, empty, and success states are handled124- [ ] Async work is not triggered redundantly125- [ ] User actions cannot leave stale state behind126127Quality:128- [ ] MVVM boundaries remain clear129- [ ] Observation API usage is consistent130- [ ] Factory-based dependencies are resolved intentionally131- [ ] Accessibility is covered132- [ ] Previews or equivalent fast feedback paths exist where appropriate133- [ ] Tests cover critical state transitions and presentation logic134```135136## Anti-Patterns to Avoid137138| Anti-Pattern | Instead Do |139|---|---|140| One giant screen view | Split into subviews with clear responsibilities |141| Duplicate booleans for the same presentation state | Use one explicit routing/presentation model |142| Business logic in `body` | Move logic into the `ViewModel` or focused helpers |143| Hidden side effects in view builders | Trigger side effects intentionally and visibly |144| Legacy observation added to new iOS 17-only features without reason | Use `@Observable` and `@Bindable` consistently |145| Resolving dependencies all over the feature | Inject collaborators cleanly with `Factory` and clear ownership |146| New app-wide state pattern for one feature | Reuse the project's existing pattern |147148## Connected Skills149150- `swiftui-specialist` - Apple best practices for view structure, data flow, `@Environment`/`@Entry`, `ForEach` identity, animations, and soft-deprecated APIs151- `swiftui-whats-new-27` - new SDK 27 SwiftUI APIs and `@State`/`@ContentBuilder` migration errors152- `building-document-based-swiftui-applications` - document-based apps153- `swift-localization` - use when the task involves multilingual strings, String Catalogs, or locale-aware UI behavior154- `technical-context-discovery` - follow project iOS conventions before editing155- `frontend-implementation` - apply UI quality and accessibility rules156- `ui-verification` - verify screen output against approved designs when relevant