--- name: swiftui-expert-skill description: Write, review, or improve SwiftUI code following best practices for state management, view composition, performance, modern APIs, Swift concurrency, and iOS 26+ Liquid Glass adoption. Use when building new SwiftUI features, refactoring existing views, reviewing code quality, or adopting modern SwiftUI patterns. --- # SwiftUI Expert Skill ## Overview Use this skill to build, review, or improve SwiftUI features with correct state management, modern API usage, Swift concurrency best practices, optimal view composition, and iOS 26+ Liquid Glass styling. Prioritize native APIs, Apple design guidance, and performance-conscious patterns. This skill focuses on facts and best practices without enforcing specific architectural patterns. ## Workflow Decision Tree ### 1) Review existing SwiftUI code - Check property wrapper usage against the selection guide (see `references/state-management.md`) - Verify modern API usage (see `references/modern-apis.md`) - Verify view composition follows extraction rules (see `references/view-structure.md`) - Check performance patterns are applied (see `references/performance-patterns.md`) - Verify list patterns use stable identity (see `references/list-patterns.md`) - Inspect Liquid Glass usage for correctness and consistency (see `references/liquid-glass.md`) - Validate iOS 26+ availability handling with sensible fallbacks ### 2) Improve existing SwiftUI code - Audit state management for correct wrapper selection (prefer `@Observable` over `ObservableObject`) - Replace deprecated APIs with modern equivalents (see `references/modern-apis.md`) - Extract complex views into separate subviews (see `references/view-structure.md`) - Refactor hot paths to minimize redundant state updates (see `references/performance-patterns.md`) - Ensure ForEach uses stable identity (see `references/list-patterns.md`) - Suggest image downsampling when `UIImage(data:)` is used (as optional optimization, see `references/image-optimization.md`) - Adopt Liquid Glass only when explicitly requested by the user ### 3) Implement new SwiftUI feature - Design data flow first: identify owned vs injected state (see `references/state-management.md`) - Use modern APIs (no deprecated modifiers or patterns, see `references/modern-apis.md`) - Use `@Observable` for shared state (with `@MainActor` if not using default actor isolation) - Structure views for optimal diffing (extract subviews early, keep views small, see `references/view-structure.md`) - Separate business logic into testable models (see `references/layout-best-practices.md`) - Apply glass effects after layout/appearance modifiers (see `references/liquid-glass.md`) - Gate iOS 26+ features with `#available` and provide fallbacks ## Core Guidelines ### State Management - **Always prefer `@Observable` over `ObservableObject`** for new code - **Mark `@Observable` classes with `@MainActor`** unless using default actor isolation - **Always mark `@State` and `@StateObject` as `private`** (makes dependencies clear) - **Never declare passed values as `@State` or `@StateObject`** (they only accept initial values) - Use `@State` with `@Observable` classes (not `@StateObject`) - `@Binding` only when child needs to **modify** parent state - `@Bindable` for injected `@Observable` objects needing bindings - Use `let` for read-only values; `var` + `.onChange()` for reactive reads - Legacy: `@StateObject` for owned `ObservableObject`; `@ObservedObject` for injected - Nested `ObservableObject` doesn't work (pass nested objects directly); `@Observable` handles nesting fine ### Modern APIs - Use `foregroundStyle()` instead of `foregroundColor()` - Use `clipShape(.rect(cornerRadius:))` instead of `cornerRadius()` - Use `Tab` API instead of `tabItem()` - Use `Button` instead of `onTapGesture()` (unless need location/count) - Use `NavigationStack` instead of `NavigationView` - Use `navigationDestination(for:)` for type-safe navigation - Use two-parameter or no-parameter `onChange()` variant - Use `ImageRenderer` for rendering SwiftUI views - Use `.sheet(item:)` instead of `.sheet(isPresented:)` for model-based content - Sheets should own their actions and call `dismiss()` internally - Use `ScrollViewReader` for programmatic scrolling with stable IDs - Avoid `UIScreen.main.bounds` for sizing - Avoid `GeometryReader` when alternatives exist (e.g., `containerRelativeFrame()`) ### Swift Best Practices - Use modern Text formatting (`.format` parameters, not `String(format:)`) - Use `localizedStandardContains()` for user-input filtering (not `contains()`) - Prefer static member lookup (`.blue` vs `Color.blue`) - Use `.task` modifier for automatic cancellation of async work - Use `.task(id:)` for value-dependent tasks ### View Composition - **Prefer modifiers over conditional views** for state changes (maintains view identity) - Extract complex views into separate subviews for better readability and performance - Keep views small for optimal performance - Keep view `body` simple and pure (no side effects or complex logic) - Use `@ViewBuilder` functions only for small, simple sections - Prefer `@ViewBuilder let content: Content` over closure-based content properties - Separate business logic into testable models (not about enforcing architectures) - Action handlers should reference methods, not contain inline logic - Use relative layout over hard-coded constants - Views should work in any context (don't assume screen size or presentation style) ### Performance - Pass only needed values to views (avoid large "config" or "context" objects) - Eliminate unnecessary dependencies to reduce update fan-out - Check for value changes before assigning state in hot paths - Avoid redundant state updates in `onReceive`, `onChange`, scroll handlers - Minimize work in frequently executed code paths - Use `LazyVStack`/`LazyHStack` for large lists - Use stable identity for `ForEach` (never `.indices` for dynamic content) - Ensure constant number of views per `ForEach` element - Avoid inline filtering in `ForEach` (prefilter and cache) - Avoid `AnyView` in list rows - Consider POD views for fast diffing (or wrap expensive views in POD parents) - Suggest image downsampling when `UIImage(data:)` is encountered (as optional optimization) - Avoid layout thrash (deep hierarchies, excessive `GeometryReader`) - Gate frequent geometry updates by thresholds - Use `Self._printChanges()` to debug unexpected view updates ### Liquid Glass (iOS 26+) **Only adopt when explicitly requested by the user.** - Use native `glassEffect`, `GlassEffectContainer`, and glass button styles - Wrap multiple glass elements in `GlassEffectContainer` - Apply `.glassEffect()` after layout and visual modifiers - Use `.interactive()` only for tappable/focusable elements - Use `glassEffectID` with `@Namespace` for morphing transitions ## Quick Reference ### Property Wrapper Selection (Modern) | Wrapper | Use When | |---------|----------| | `@State` | Internal view state (must be `private`), or owned `@Observable` class | | `@Binding` | Child modifies parent's state | | `@Bindable` | Injected `@Observable` needing bindings | | `let` | Read-only value from parent | | `var` | Read-only value watched via `.onChange()` | **Legacy (Pre-iOS 17):** | Wrapper | Use When | |---------|----------| | `@StateObject` | View owns an `ObservableObject` (use `@State` with `@Observable` instead) | | `@ObservedObject` | View receives an `ObservableObject` | ### Modern API Replacements | Deprecated | Modern Alternative | |------------|-------------------| | `foregroundColor()` | `foregroundStyle()` | | `cornerRadius()` | `clipShape(.rect(cornerRadius:))` | | `tabItem()` | `Tab` API | | `onTapGesture()` | `Button` (unless need location/count) | | `NavigationView` | `NavigationStack` | | `onChange(of:) { value in }` | `onChange(of:) { old, new in }` or `onChange(of:) { }` | | `fontWeight(.bold)` | `bold()` | | `GeometryReader` | `containerRelativeFrame()` or `visualEffect()` | | `showsIndicators: false` | `.scrollIndicators(.hidden)` | | `String(format: "%.2f", value)` | `Text(value, format: .number.precision(.fractionLength(2)))` | | `string.contains(search)` | `string.localizedStandardContains(search)` (for user input) | ### Liquid Glass Patterns ```swift // Basic glass effect with fallback if #available(iOS 26, *) { content .padding() .glassEffect(.regular.interactive(), in: .rect(cornerRadius: 16)) } else { content .padding() .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 16)) } // Grouped glass elements GlassEffectContainer(spacing: 24) { HStack(spacing: 24) { GlassButton1() GlassButton2() } } // Glass buttons Button("Confirm") { } .buttonStyle(.glassProminent) ``` ## Review Checklist ### State Management - [ ] Using `@Observable` instead of `ObservableObject` for new code - [ ] `@Observable` classes marked with `@MainActor` (if needed) - [ ] Using `@State` with `@Observable` classes (not `@StateObject`) - [ ] `@State` and `@StateObject` properties are `private` - [ ] Passed values NOT declared as `@State` or `@StateObject` - [ ] `@Binding` only where child modifies parent state - [ ] `@Bindable` for injected `@Observable` needing bindings - [ ] Nested `ObservableObject` avoided (or passed directly to child views) ### Modern APIs (see `references/modern-apis.md`) - [ ] Using `foregroundStyle()` instead of `foregroundColor()` - [ ] Using `clipShape(.rect(cornerRadius:))` instead of `cornerRadius()` - [ ] Using `Tab` API instead of `tabItem()` - [ ] Using `Button` instead of `onTapGesture()` (unless need location/count) - [ ] Using `NavigationStack` instead of `NavigationView` - [ ] Avoiding `UIScreen.main.bounds` - [ ] Using alternatives to `GeometryReader` when possible - [ ] Button images include text labels for accessibility ### Sheets & Navigation (see `references/sheet-navigation-patterns.md`) - [ ] Using `.sheet(item:)` for model-based sheets - [ ] Sheets own their actions and dismiss internally - [ ] Using `navigationDestination(for:)` for type-safe navigation ### ScrollView (see `references/scroll-patterns.md`) - [ ] Using `ScrollViewReader` with stable IDs for programmatic scrolling - [ ] Using `.scrollIndicators(.hidden)` instead of initializer parameter ### Text & Formatting (see `references/text-formatting.md`) - [ ] Using modern Text formatting (not `String(format:)`) - [ ] Using `localizedStandardContains()` for search filtering ### View Structure (see `references/view-structure.md`) - [ ] Using modifiers instead of conditionals for state changes - [ ] Complex views extracted to separate subviews - [ ] Views kept small for performance - [ ] Container views use `@ViewBuilder let content: Content` ### Performance (see `references/performance-patterns.md`) - [ ] View `body` kept simple and pure (no side effects) - [ ] Passing only needed values (not large config objects) - [ ] Eliminating unnecessary dependencies - [ ] State updates check for value changes before assigning - [ ] Hot paths minimize state updates - [ ] No object creation in `body` - [ ] Heavy computation moved out of `body` ### List Patterns (see `references/list-patterns.md`) - [ ] ForEach uses stable identity (not `.indices`) - [ ] Constant number of views per ForEach element - [ ] No inline filtering in ForEach - [ ] No `AnyView` in list rows ### Layout (see `references/layout-best-practices.md`) - [ ] Avoiding layout thrash (deep hierarchies, excessive GeometryReader) - [ ] Gating frequent geometry updates by thresholds - [ ] Business logic separated into testable models - [ ] Action handlers reference methods (not inline logic) - [ ] Using relative layout (not hard-coded constants) - [ ] Views work in any context (context-agnostic) ### Liquid Glass (iOS 26+) - [ ] `#available(iOS 26, *)` with fallback for Liquid Glass - [ ] Multiple glass views wrapped in `GlassEffectContainer` - [ ] `.glassEffect()` applied after layout/appearance modifiers - [ ] `.interactive()` only on user-interactable elements - [ ] Shapes and tints consistent across related elements ## References - `references/state-management.md` - Property wrappers and data flow (prefer `@Observable`) - `references/view-structure.md` - View composition, extraction, and container patterns - `references/performance-patterns.md` - Performance optimization techniques and anti-patterns - `references/list-patterns.md` - ForEach identity, stability, and list best practices - `references/layout-best-practices.md` - Layout patterns, context-agnostic views, and testability - `references/modern-apis.md` - Modern API usage and deprecated replacements - `references/sheet-navigation-patterns.md` - Sheet presentation and navigation patterns - `references/scroll-patterns.md` - ScrollView patterns and programmatic scrolling - `references/text-formatting.md` - Modern text formatting and string operations - `references/image-optimization.md` - AsyncImage, image downsampling, and optimization - `references/liquid-glass.md` - iOS 26+ Liquid Glass API ## Philosophy This skill focuses on **facts and best practices**, not architectural opinions: - We don't enforce specific architectures (e.g., MVVM, VIPER) - We do encourage separating business logic for testability - We prioritize modern APIs over deprecated ones - We emphasize thread safety with `@MainActor` and `@Observable` - We optimize for performance and maintainability - We follow Apple's Human Interface Guidelines and API design patterns