$npx -y skills add johnrogers/claude-swift-engineering --skill swiftui-patternsUse when implementing iOS 17+ SwiftUI patterns: @Observable/@Bindable, MVVM architecture, NavigationStack, lazy loading, UIKit interop, accessibility (VoiceOver/Dynamic Type), async operations (.task/.refreshable), or migrating from ObservableObject/@StateObject.
| 1 | # SwiftUI Patterns (iOS 17+) |
| 2 | |
| 3 | SwiftUI 17+ removes ObservableObject boilerplate with @Observable, simplifies environment injection with @Environment, and introduces task-based async patterns. The core principle: use Apple's modern APIs instead of reactive libraries. |
| 4 | |
| 5 | ## Overview |
| 6 | |
| 7 | ## Quick Reference |
| 8 | |
| 9 | | Need | Use (iOS 17+) | NOT | |
| 10 | |------|---------------|-----| |
| 11 | | Observable model | `@Observable` | `ObservableObject` | |
| 12 | | Published property | Regular property | `@Published` | |
| 13 | | Own state | `@State` | `@StateObject` | |
| 14 | | Passed model (binding) | `@Bindable` | `@ObservedObject` | |
| 15 | | Environment injection | `environment(_:)` | `environmentObject(_:)` | |
| 16 | | Environment access | `@Environment(Type.self)` | `@EnvironmentObject` | |
| 17 | | Async on appear | `.task { }` | `.onAppear { Task {} }` | |
| 18 | | Value change | `onChange(of:initial:_:)` | `onChange(of:perform:)` | |
| 19 | |
| 20 | ## Core Workflow |
| 21 | |
| 22 | 1. Use `@Observable` for model classes (no @Published needed) |
| 23 | 2. Use `@State` for view-owned models, `@Bindable` for passed models |
| 24 | 3. Use `.task { }` for async work (auto-cancels on disappear) |
| 25 | 4. Use `NavigationStack` with `NavigationPath` for programmatic navigation |
| 26 | 5. Apply `.accessibilityLabel()` and `.accessibilityHint()` to interactive elements |
| 27 | |
| 28 | ## Reference Loading Guide |
| 29 | |
| 30 | **ALWAYS load reference files if there is even a small chance the content may be required.** It's better to have the context than to miss a pattern or make a mistake. |
| 31 | |
| 32 | | Reference | Load When | |
| 33 | |-----------|-----------| |
| 34 | | **[Observable](references/observable.md)** | Creating new `@Observable` model classes | |
| 35 | | **[State Management](references/state-management.md)** | Deciding between `@State`, `@Bindable`, `@Environment` | |
| 36 | | **[Environment](references/environment.md)** | Injecting dependencies into view hierarchy | |
| 37 | | **[View Modifiers](references/view-modifiers.md)** | Using `onChange`, `task`, or iOS 17+ modifiers | |
| 38 | | **[Migration Guide](references/migration-guide.md)** | Updating iOS 16 code to iOS 17+ | |
| 39 | | **[MVVM Observable](references/mvvm-observable.md)** | Setting up view model architecture | |
| 40 | | **[Navigation](references/navigation.md)** | Programmatic or deep-link navigation | |
| 41 | | **[Performance](references/performance.md)** | Lists with 100+ items or excessive re-renders | |
| 42 | | **[UIKit Interop](references/uikit-interop.md)** | Wrapping UIKit components (WKWebView, PHPicker) | |
| 43 | | **[Accessibility](references/accessibility.md)** | VoiceOver, Dynamic Type, accessibility actions | |
| 44 | | **[Async Patterns](references/async-patterns.md)** | Loading states, refresh, background tasks | |
| 45 | | **[Composition](references/composition.md)** | Reusable view modifiers or complex conditional UI | |
| 46 | |
| 47 | ## Common Mistakes |
| 48 | |
| 49 | 1. **Over-using `@Bindable` for passed models** — Creating `@Bindable` for every property causes unnecessary view reloads. Use `@Bindable` only for mutable model properties that need two-way binding. Read-only computed properties should use regular properties. |
| 50 | |
| 51 | 2. **State placement errors** — Putting model state in the view instead of a dedicated `@Observable` model causes view logic to become tangled. Always separate model and view concerns. |
| 52 | |
| 53 | 3. **NavigationPath state corruption** — Mutating `NavigationPath` incorrectly can leave it in inconsistent state. Use `navigationDestination(for:destination:)` with proper state management to avoid path corruption. |
| 54 | |
| 55 | 4. **Missing `.task` cancellation** — `.task` handles cancellation on disappear automatically, but nested Tasks don't. Complex async flows need explicit cancellation tracking to avoid zombie tasks. |
| 56 | |
| 57 | 5. **Ignoring environment invalidation** — Changing environment values at parent doesn't invalidate child views automatically. Use `@Environment` consistently and understand when re-renders happen based on observation. |
| 58 | |
| 59 | 6. **UIKit interop memory leaks** — `UIViewRepresentable` and `UIViewControllerRepresentable` can leak if delegate cycles aren't broken. Weak references and explicit cleanup are required. |