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.
Add this skill
npx mdskills install AvdLee/swiftui-expert-skillComprehensive SwiftUI guidance with clear workflows, modern API migration, and performance patterns
1---2name: swiftui-expert-skill3description: 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.4---56# SwiftUI Expert Skill78## Overview9Use 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.1011## Workflow Decision Tree1213### 1) Review existing SwiftUI code14- Check property wrapper usage against the selection guide (see `references/state-management.md`)15- Verify modern API usage (see `references/modern-apis.md`)16- Verify view composition follows extraction rules (see `references/view-structure.md`)17- Check performance patterns are applied (see `references/performance-patterns.md`)18- Verify list patterns use stable identity (see `references/list-patterns.md`)19- Check animation patterns for correctness (see `references/animation-basics.md`, `references/animation-transitions.md`)20- Inspect Liquid Glass usage for correctness and consistency (see `references/liquid-glass.md`)21- Validate iOS 26+ availability handling with sensible fallbacks2223### 2) Improve existing SwiftUI code24- Audit state management for correct wrapper selection (prefer `@Observable` over `ObservableObject`)25- Replace deprecated APIs with modern equivalents (see `references/modern-apis.md`)26- Extract complex views into separate subviews (see `references/view-structure.md`)27- Refactor hot paths to minimize redundant state updates (see `references/performance-patterns.md`)28- Ensure ForEach uses stable identity (see `references/list-patterns.md`)29- Improve animation patterns (use value parameter, proper transitions, see `references/animation-basics.md`, `references/animation-transitions.md`)30- Suggest image downsampling when `UIImage(data:)` is used (as optional optimization, see `references/image-optimization.md`)31- Adopt Liquid Glass only when explicitly requested by the user3233### 3) Implement new SwiftUI feature34- Design data flow first: identify owned vs injected state (see `references/state-management.md`)35- Use modern APIs (no deprecated modifiers or patterns, see `references/modern-apis.md`)36- Use `@Observable` for shared state (with `@MainActor` if not using default actor isolation)37- Structure views for optimal diffing (extract subviews early, keep views small, see `references/view-structure.md`)38- Separate business logic into testable models (see `references/layout-best-practices.md`)39- Use correct animation patterns (implicit vs explicit, transitions, see `references/animation-basics.md`, `references/animation-transitions.md`, `references/animation-advanced.md`)40- Apply glass effects after layout/appearance modifiers (see `references/liquid-glass.md`)41- Gate iOS 26+ features with `#available` and provide fallbacks4243## Core Guidelines4445### State Management46- **Always prefer `@Observable` over `ObservableObject`** for new code47- **Mark `@Observable` classes with `@MainActor`** unless using default actor isolation48- **Always mark `@State` and `@StateObject` as `private`** (makes dependencies clear)49- **Never declare passed values as `@State` or `@StateObject`** (they only accept initial values)50- Use `@State` with `@Observable` classes (not `@StateObject`)51- `@Binding` only when child needs to **modify** parent state52- `@Bindable` for injected `@Observable` objects needing bindings53- Use `let` for read-only values; `var` + `.onChange()` for reactive reads54- Legacy: `@StateObject` for owned `ObservableObject`; `@ObservedObject` for injected55- Nested `ObservableObject` doesn't work (pass nested objects directly); `@Observable` handles nesting fine5657### Modern APIs58- Use `foregroundStyle()` instead of `foregroundColor()`59- Use `clipShape(.rect(cornerRadius:))` instead of `cornerRadius()`60- Use `Tab` API instead of `tabItem()`61- Use `Button` instead of `onTapGesture()` (unless need location/count)62- Use `NavigationStack` instead of `NavigationView`63- Use `navigationDestination(for:)` for type-safe navigation64- Use two-parameter or no-parameter `onChange()` variant65- Use `ImageRenderer` for rendering SwiftUI views66- Use `.sheet(item:)` instead of `.sheet(isPresented:)` for model-based content67- Sheets should own their actions and call `dismiss()` internally68- Use `ScrollViewReader` for programmatic scrolling with stable IDs69- Avoid `UIScreen.main.bounds` for sizing70- Avoid `GeometryReader` when alternatives exist (e.g., `containerRelativeFrame()`)7172### Swift Best Practices73- Use modern Text formatting (`.format` parameters, not `String(format:)`)74- Use `localizedStandardContains()` for user-input filtering (not `contains()`)75- Prefer static member lookup (`.blue` vs `Color.blue`)76- Use `.task` modifier for automatic cancellation of async work77- Use `.task(id:)` for value-dependent tasks7879### View Composition80- **Prefer modifiers over conditional views** for state changes (maintains view identity)81- Extract complex views into separate subviews for better readability and performance82- Keep views small for optimal performance83- Keep view `body` simple and pure (no side effects or complex logic)84- Use `@ViewBuilder` functions only for small, simple sections85- Prefer `@ViewBuilder let content: Content` over closure-based content properties86- Separate business logic into testable models (not about enforcing architectures)87- Action handlers should reference methods, not contain inline logic88- Use relative layout over hard-coded constants89- Views should work in any context (don't assume screen size or presentation style)9091### Performance92- Pass only needed values to views (avoid large "config" or "context" objects)93- Eliminate unnecessary dependencies to reduce update fan-out94- Check for value changes before assigning state in hot paths95- Avoid redundant state updates in `onReceive`, `onChange`, scroll handlers96- Minimize work in frequently executed code paths97- Use `LazyVStack`/`LazyHStack` for large lists98- Use stable identity for `ForEach` (never `.indices` for dynamic content)99- Ensure constant number of views per `ForEach` element100- Avoid inline filtering in `ForEach` (prefilter and cache)101- Avoid `AnyView` in list rows102- Consider POD views for fast diffing (or wrap expensive views in POD parents)103- Suggest image downsampling when `UIImage(data:)` is encountered (as optional optimization)104- Avoid layout thrash (deep hierarchies, excessive `GeometryReader`)105- Gate frequent geometry updates by thresholds106- Use `Self._printChanges()` to debug unexpected view updates107108### Animations109- Use `.animation(_:value:)` with value parameter (deprecated version without value is too broad)110- Use `withAnimation` for event-driven animations (button taps, gestures)111- Prefer transforms (`offset`, `scale`, `rotation`) over layout changes (`frame`) for performance112- Transitions require animations outside the conditional structure113- Custom `Animatable` implementations must have explicit `animatableData`114- Use `.phaseAnimator` for multi-step sequences (iOS 17+)115- Use `.keyframeAnimator` for precise timing control (iOS 17+)116- Animation completion handlers need `.transaction(value:)` for reexecution117- Implicit animations override explicit animations (later in view tree wins)118119### Liquid Glass (iOS 26+)120**Only adopt when explicitly requested by the user.**121- Use native `glassEffect`, `GlassEffectContainer`, and glass button styles122- Wrap multiple glass elements in `GlassEffectContainer`123- Apply `.glassEffect()` after layout and visual modifiers124- Use `.interactive()` only for tappable/focusable elements125- Use `glassEffectID` with `@Namespace` for morphing transitions126127## Quick Reference128129### Property Wrapper Selection (Modern)130| Wrapper | Use When |131|---------|----------|132| `@State` | Internal view state (must be `private`), or owned `@Observable` class |133| `@Binding` | Child modifies parent's state |134| `@Bindable` | Injected `@Observable` needing bindings |135| `let` | Read-only value from parent |136| `var` | Read-only value watched via `.onChange()` |137138**Legacy (Pre-iOS 17):**139| Wrapper | Use When |140|---------|----------|141| `@StateObject` | View owns an `ObservableObject` (use `@State` with `@Observable` instead) |142| `@ObservedObject` | View receives an `ObservableObject` |143144### Modern API Replacements145| Deprecated | Modern Alternative |146|------------|-------------------|147| `foregroundColor()` | `foregroundStyle()` |148| `cornerRadius()` | `clipShape(.rect(cornerRadius:))` |149| `tabItem()` | `Tab` API |150| `onTapGesture()` | `Button` (unless need location/count) |151| `NavigationView` | `NavigationStack` |152| `onChange(of:) { value in }` | `onChange(of:) { old, new in }` or `onChange(of:) { }` |153| `fontWeight(.bold)` | `bold()` |154| `GeometryReader` | `containerRelativeFrame()` or `visualEffect()` |155| `showsIndicators: false` | `.scrollIndicators(.hidden)` |156| `String(format: "%.2f", value)` | `Text(value, format: .number.precision(.fractionLength(2)))` |157| `string.contains(search)` | `string.localizedStandardContains(search)` (for user input) |158159### Liquid Glass Patterns160```swift161// Basic glass effect with fallback162if #available(iOS 26, *) {163 content164 .padding()165 .glassEffect(.regular.interactive(), in: .rect(cornerRadius: 16))166} else {167 content168 .padding()169 .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 16))170}171172// Grouped glass elements173GlassEffectContainer(spacing: 24) {174 HStack(spacing: 24) {175 GlassButton1()176 GlassButton2()177 }178}179180// Glass buttons181Button("Confirm") { }182 .buttonStyle(.glassProminent)183```184185## Review Checklist186187### State Management188- [ ] Using `@Observable` instead of `ObservableObject` for new code189- [ ] `@Observable` classes marked with `@MainActor` (if needed)190- [ ] Using `@State` with `@Observable` classes (not `@StateObject`)191- [ ] `@State` and `@StateObject` properties are `private`192- [ ] Passed values NOT declared as `@State` or `@StateObject`193- [ ] `@Binding` only where child modifies parent state194- [ ] `@Bindable` for injected `@Observable` needing bindings195- [ ] Nested `ObservableObject` avoided (or passed directly to child views)196197### Modern APIs (see `references/modern-apis.md`)198- [ ] Using `foregroundStyle()` instead of `foregroundColor()`199- [ ] Using `clipShape(.rect(cornerRadius:))` instead of `cornerRadius()`200- [ ] Using `Tab` API instead of `tabItem()`201- [ ] Using `Button` instead of `onTapGesture()` (unless need location/count)202- [ ] Using `NavigationStack` instead of `NavigationView`203- [ ] Avoiding `UIScreen.main.bounds`204- [ ] Using alternatives to `GeometryReader` when possible205- [ ] Button images include text labels for accessibility206207### Sheets & Navigation (see `references/sheet-navigation-patterns.md`)208- [ ] Using `.sheet(item:)` for model-based sheets209- [ ] Sheets own their actions and dismiss internally210- [ ] Using `navigationDestination(for:)` for type-safe navigation211212### ScrollView (see `references/scroll-patterns.md`)213- [ ] Using `ScrollViewReader` with stable IDs for programmatic scrolling214- [ ] Using `.scrollIndicators(.hidden)` instead of initializer parameter215216### Text & Formatting (see `references/text-formatting.md`)217- [ ] Using modern Text formatting (not `String(format:)`)218- [ ] Using `localizedStandardContains()` for search filtering219220### View Structure (see `references/view-structure.md`)221- [ ] Using modifiers instead of conditionals for state changes222- [ ] Complex views extracted to separate subviews223- [ ] Views kept small for performance224- [ ] Container views use `@ViewBuilder let content: Content`225226### Performance (see `references/performance-patterns.md`)227- [ ] View `body` kept simple and pure (no side effects)228- [ ] Passing only needed values (not large config objects)229- [ ] Eliminating unnecessary dependencies230- [ ] State updates check for value changes before assigning231- [ ] Hot paths minimize state updates232- [ ] No object creation in `body`233- [ ] Heavy computation moved out of `body`234235### List Patterns (see `references/list-patterns.md`)236- [ ] ForEach uses stable identity (not `.indices`)237- [ ] Constant number of views per ForEach element238- [ ] No inline filtering in ForEach239- [ ] No `AnyView` in list rows240241### Layout (see `references/layout-best-practices.md`)242- [ ] Avoiding layout thrash (deep hierarchies, excessive GeometryReader)243- [ ] Gating frequent geometry updates by thresholds244- [ ] Business logic separated into testable models245- [ ] Action handlers reference methods (not inline logic)246- [ ] Using relative layout (not hard-coded constants)247- [ ] Views work in any context (context-agnostic)248249### Animations (see `references/animation-basics.md`, `references/animation-transitions.md`, `references/animation-advanced.md`)250- [ ] Using `.animation(_:value:)` with value parameter251- [ ] Using `withAnimation` for event-driven animations252- [ ] Transitions paired with animations outside conditional structure253- [ ] Custom `Animatable` has explicit `animatableData` implementation254- [ ] Preferring transforms over layout changes for animation performance255- [ ] Phase animations for multi-step sequences (iOS 17+)256- [ ] Keyframe animations for precise timing (iOS 17+)257- [ ] Completion handlers use `.transaction(value:)` for reexecution258259### Liquid Glass (iOS 26+)260- [ ] `#available(iOS 26, *)` with fallback for Liquid Glass261- [ ] Multiple glass views wrapped in `GlassEffectContainer`262- [ ] `.glassEffect()` applied after layout/appearance modifiers263- [ ] `.interactive()` only on user-interactable elements264- [ ] Shapes and tints consistent across related elements265266## References267- `references/state-management.md` - Property wrappers and data flow (prefer `@Observable`)268- `references/view-structure.md` - View composition, extraction, and container patterns269- `references/performance-patterns.md` - Performance optimization techniques and anti-patterns270- `references/list-patterns.md` - ForEach identity, stability, and list best practices271- `references/layout-best-practices.md` - Layout patterns, context-agnostic views, and testability272- `references/modern-apis.md` - Modern API usage and deprecated replacements273- `references/animation-basics.md` - Core animation concepts, implicit/explicit animations, timing, performance274- `references/animation-transitions.md` - Transitions, custom transitions, Animatable protocol275- `references/animation-advanced.md` - Transactions, phase/keyframe animations (iOS 17+), completion handlers (iOS 17+)276- `references/sheet-navigation-patterns.md` - Sheet presentation and navigation patterns277- `references/scroll-patterns.md` - ScrollView patterns and programmatic scrolling278- `references/text-formatting.md` - Modern text formatting and string operations279- `references/image-optimization.md` - AsyncImage, image downsampling, and optimization280- `references/liquid-glass.md` - iOS 26+ Liquid Glass API281282## Philosophy283284This skill focuses on **facts and best practices**, not architectural opinions:285- We don't enforce specific architectures (e.g., MVVM, VIPER)286- We do encourage separating business logic for testability287- We prioritize modern APIs over deprecated ones288- We emphasize thread safety with `@MainActor` and `@Observable`289- We optimize for performance and maintainability290- We follow Apple's Human Interface Guidelines and API design patterns291
Full transparency — inspect the skill content before installing.