byavdlee· 55 skills
Use when writing, reviewing, or refactoring SwiftUI code for iOS or macOS, including state management and @Observable data flow, view composition and invalidation/performance, lists and ForEach identity, environment usage, localization, animations, Liquid Glass adoption, migrating soft-deprecated APIs, or Instruments .trace capture/analysis for hangs, hitches, CPU hotspots, or
$npx -y skills add avdlee/swiftui-agent-skill --skill swiftui-expert-skillInstalls into the current project.
Run `npx skills use "https://github.com/avdlee/swiftui-agent-skill" --skill "avdlee/swiftui-agent-skill/swiftui-expert-skill"` and follow the generated skill instructions now. Read its complete output, redirecting it to a temporary file first if necessary. Resolve relative paths from the supporting-files directory it provides.
Use the skills in "https://github.com/avdlee/swiftui-agent-skill" that are relevant to the current task. Run `npx skills add "https://github.com/avdlee/swiftui-agent-skill"` and select the relevant skills, then follow their instructions.
| 1 | # SwiftUI Expert Skill |
| 2 | |
| 3 | ## Operating Rules |
| 4 | |
| 5 | - Consult `references/latest-apis.md` at the start of every task to avoid deprecated APIs |
| 6 | - Prefer native SwiftUI APIs over UIKit/AppKit bridging unless bridging is necessary |
| 7 | - Focus on correctness and performance; do not enforce specific architectures (MVVM, VIPER, etc.) |
| 8 | - Encourage separating business logic from views for testability without mandating how |
| 9 | - Follow Apple's Human Interface Guidelines and API design patterns |
| 10 | - Only adopt Liquid Glass when explicitly requested by the user (see `references/liquid-glass.md`) |
| 11 | - Present performance optimizations as suggestions, not requirements |
| 12 | - Use `#available` gating with sensible fallbacks for version-specific APIs |
| 13 | |
| 14 | ## Task Workflow |
| 15 | |
| 16 | ### Review existing SwiftUI code |
| 17 | - Read the code under review and identify which topics apply |
| 18 | - Flag deprecated APIs (compare against `references/latest-apis.md`) |
| 19 | - Run the Topic Router below for each relevant topic |
| 20 | - Validate `#available` gating and fallback paths for iOS 26+ features |
| 21 | |
| 22 | ### Improve existing SwiftUI code |
| 23 | - Audit current implementation against the Topic Router topics |
| 24 | - Replace deprecated APIs with modern equivalents from `references/latest-apis.md` |
| 25 | - Refactor hot paths to reduce unnecessary state updates |
| 26 | - Extract complex view bodies into separate subviews |
| 27 | - Suggest image downsampling when `UIImage(data:)` is encountered (optional optimization, see `references/image-optimization.md`) |
| 28 | |
| 29 | ### Implement new SwiftUI feature |
| 30 | - Design data flow first: identify owned vs injected state |
| 31 | - Structure views for optimal diffing (extract subviews early) |
| 32 | - Apply correct animation patterns (implicit vs explicit, transitions) |
| 33 | - Use `Button` for all tappable elements; add accessibility grouping and labels |
| 34 | - Gate version-specific APIs with `#available` and provide fallbacks |
| 35 | |
| 36 | ### Record a new Instruments trace |
| 37 | Trigger when the user asks to "record a trace", "profile the app", "capture a session", etc. Full reference: `references/trace-recording.md`. |
| 38 | |
| 39 | 1. **Confirm target** — attach to a running app, launch an app, or record all processes? If the user didn't say, ask. List connected devices when useful: |
| 40 | ```bash |
| 41 | python3 "${SKILL_DIR}/scripts/record_trace.py" --list-devices |
| 42 | ``` |
| 43 | 2. **Pick a template based on target kind** — the `SwiftUI` template populates the SwiftUI lane on any **real device**: a physical iOS/iPadOS device **or the host Mac**. The only exception is the **iOS Simulator**, where the SwiftUI lane comes back empty — switch to `--template "Time Profiler"` in that case (still gives Time Profiler + Hangs + Animation Hitches). Always check `--list-devices`: `simulators` kind → `Time Profiler`; `devices` kind (real devices and the host Mac) → default `SwiftUI`. Full decision table in `references/trace-recording.md`. |
| 44 | 3. **Start the recording**. For agent-driven sessions where the user says "I'll tell you when I'm done", start in the background and use a stop-file: |
| 45 | ```bash |
| 46 | python3 "${SKILL_DIR}/scripts/record_trace.py" \ |
| 47 | --device "<name|udid>" --attach "<AppName>" \ |
| 48 | --stop-file /tmp/stop-trace --output ~/Desktop/session.trace |
| 49 | ``` |
| 50 | For interactive sessions, just tell the user to press Ctrl+C when done. |
| 51 | 4. **Signal stop** — when the user says they've finished exercising the app, `touch /tmp/stop-trace`. The script cleanly SIGINTs xctrace and waits up to 60s for finalisation. |
| 52 | 5. **Analyse** the resulting trace (flow into the "Trace-driven improvement" workflow below). |
| 53 | |
| 54 | ### Trace-driven improvement (Instruments `.trace` provided) |
| 55 | Trigger whenever the user's request references a `.trace` file. A target SwiftUI source file is **optional** — if given, cite specific lines; if not, recommend where to look based on view names and symbols the trace already reveals. |
| 56 | |
| 57 | Full reference: `references/trace-analysis.md`. Summary of the composition pattern: |
| 58 | |
| 59 | 1. **Scope the analysis.** Ask yourself: does the user want the whole trace, or a slice? |
| 60 | - "focus on X / after X / between X and Y / during X" → **resolve to a window first** (see step 2). |
| 61 | - No scoping cue → analyse the whole trace. |
| 62 | 2. **Resolve a window (only if the user scoped).** The parser exposes two discovery modes: |
| 63 | ```bash |
| 64 | # Find a log that marks the start/end of the region of interest: |
| 65 | python3 "${SKILL_DIR}/scripts/analyze_trace.py" --trace <path> \ |
| 66 | --list-logs --log-message-contains "loaded feed" --log-limit 5 |
| 67 | # Or list os_signpost intervals (paired begin/end), filterable by name: |
| 68 | python3 "${SKILL_DIR}/scripts/analyze_trace.py" --trace <path> \ |
| 69 | --list-signposts --signpost-name-contains "ImageDecode" |
| 70 | ``` |
| 71 | Both modes accept `--window START_MS:END_MS` to scope discovery. Pick the `time_ms` (for logs) or `start_ms`/`end_ms` (for signposts) that match the user's description. Build a window like `--window 10400:11700`. |
| 72 | 3. **Run the main analysis** (with or without `--window`): |
| 73 | ```bash |
| 74 | python3 "${SKILL_DIR}/scripts/analyze_trace.py" --trace <path> \ |
| 75 | --json-only --top 10 [--window START_MS:END_MS] |
| 76 | ``` |
| 77 | 4. **Interpret with `references/trace-analysis.md`** — key diagnostics: |
| 78 | - `main_running_coverage_pct` inside each correlation (<25% = blocked; ≥75% = CPU-bound). |
| 79 | - `swiftui-causes.top_sources` reveals *why* updates keep happening — high-edge-count sources like `UserDefaultObserver.send()` or wide `EnvironmentWriter` entries are structural invalidation bugs. Fixing one often collapses many downstream hot views. |
| 80 | 5. **When a specific view shows as expensive, ask who's invalidating it.** Use `--fanin-for "<view name>"` to get the ranked list of source nodes driving the updates. |
| 81 | 6. **Optionally ground in source.** If the user pointed at a file, read it and match view names / user-code symbols against identifiers there. If not, recommend which files to open based on the view names SwiftUI reported. |
| 82 | 7. **Return a prioritised plan.** Cite evidence (coverage %, hot symbol, overlapping view, log timestamp, cause-graph edges) and route each recommendation to a Topic Router reference. |
| 83 | 8. Only edit code if the user asked for edits. |
| 84 | |
| 85 | ### Topic Router |
| 86 | |
| 87 | Consult the reference file for each topic relevant to the current task: |
| 88 | |
| 89 | | Topic | Reference | |
| 90 | |-------|-----------| |
| 91 | | State management | `references/state-management.md` | |
| 92 | | View composition | `references/view-structure.md` | |
| 93 | | Performance | `references/performance-patterns.md` | |
| 94 | | Lists and ForEach | `references/list-patterns.md` | |
| 95 | | Layout | `references/layout-best-practices.md` | |
| 96 | | Sheets and navigation | `references/sheet-navigation-patterns.md` | |
| 97 | | ScrollView, scroll position, and scroll geometry | `references/scroll-patterns.md` | |
| 98 | | Focus management | `references/focus-patterns.md` | |
| 99 | | Animations (basics) | `references/animation-basics.md` | |
| 100 | | Animations (transitions) | `references/animation-transitions.md` | |
| 101 | | Animations (advanced) | `references/animation-advanced.md` | |
| 102 | | Accessibility | `references/accessibility-patterns.md` | |
| 103 | | Swift Charts | `references/charts.md` | |
| 104 | | Charts accessibility | `references/charts-accessibility.md` | |
| 105 | | Image optimization | `references/image-optimization.md` | |
| 106 | | Liquid Glass (iOS 26+) | `references/liquid-glass.md` | |
| 107 | | macOS scenes | `references/macos-scenes.md` | |
| 108 | | macOS window styling | `references/macos-window-styling.md` | |
| 109 | | macOS views | `references/macos-views.md` | |
| 110 | | Text patterns | `references/text-patterns.md` | |
| 111 | | Localization | `references/localization.md` | |
| 112 | | Deprecated API lookup | `references/latest-apis.md` | |
| 113 | | Handling soft-deprecated APIs | `references/soft-deprecation.md` | |
| 114 | | Previews | `references/previews.md` | |
| 115 | | Instruments trace analysis | `references/trace-analysis.md` | |
| 116 | | Instruments trace recording | `references/trace-recording.md` | |
| 117 | |
| 118 | ## Correctness Checklist |
| 119 | |
| 120 | These are hard rules -- violations are always bugs: |
| 121 | |
| 122 | - [ ] `@State` properties are `private` |
| 123 | - [ ] `@Binding` only where a child modifies parent state |
| 124 | - [ ] Passed values never declared as `@State` or `@StateObject` (they ignore updates) |
| 125 | - [ ] `@StateObject` for view-owned objects; `@ObservedObject` for injected |
| 126 | - [ ] iOS 17+: `@State` with `@Observable`; `@Bindable` for injected observables needing bindings |
| 127 | - [ ] `ForEach` uses stable identity (never `.indices`/`\.offset`; id outlives the view and isn't derived from mutable content) |
| 128 | - [ ] Constant number of views per `ForEach` element; `List` rows are unary |
| 129 | - [ ] No closures stored in custom `@Environment`/`@FocusedValue` keys |
| 130 | - [ ] Custom `@Entry` default values are stable (no `Model()`/`Date()`/`UUID()` expressions) |
| 131 | - [ ] `.animation(_:value:)` always includes the `value` parameter |
| 132 | - [ ] `@FocusState` properties are `private` |
| 133 | - [ ] No redundant `@FocusState` writes inside tap gesture handlers on `.focusable()` views |
| 134 | - [ ] iOS 26+ APIs gated with `#available` and fallback provided |
| 135 | - [ ] `import Charts` present in files using chart types |
| 136 | - [ ] Previews use self-contained mock data; no dependency on live services or network |
| 137 | |
| 138 | ## References |
| 139 | |
| 140 | - `references/latest-apis.md` -- **Read first for every task.** Deprecated-to-modern API transitions (iOS 15+ through iOS 26+) |
| 141 | - `references/state-management.md` -- Property wrappers, data flow, `@Observable` migration |
| 142 | - `references/view-structure.md` -- View extraction, container patterns, `@ViewBuilder` |
| 143 | - `references/performance-patterns.md` -- Hot-path optimization, update control, `_logChanges()` |
| 144 | - `references/list-patterns.md` -- ForEach identity, Table (iOS 16+), inline filtering pitfalls |
| 145 | - `references/layout-best-practices.md` -- Layout patterns, GeometryReader alternatives |
| 146 | - `references/accessibility-patterns.md` -- VoiceOver, Dynamic Type, grouping, traits |
| 147 | - `references/animation-basics.md` -- Implicit/explicit animations, timing, performance |
| 148 | - `references/animation-transitions.md` -- View transitions, `matchedGeometryEffect`, `Animatable` |
| 149 | - `references/animation-advanced.md` -- Phase/keyframe animations (iOS 17+), `@Animatable` macro (iOS 26+) |
| 150 | - `references/charts.md` -- Swift Charts marks, axes, selection, styling, Chart3D (iOS 26+) |
| 151 | - `references/charts-accessibility.md` -- Charts VoiceOver, Audio Graph, fallback strategies |
| 152 | - `references/sheet-navigation-patterns.md` -- Sheets, NavigationSplitView, Inspector |
| 153 | - `references/scroll-patterns.md` -- ScrollViewReader, scroll geometry, programmatic scrolling, target behaviors |
| 154 | - `references/focus-patterns.md` -- Focus state, focusable views, focused values, default focus, common pitfalls |
| 155 | - `references/image-optimization.md` -- AsyncImage, downsampling, caching |
| 156 | - `references/liquid-glass.md` -- iOS 26+ Liquid Glass effects and fallback patterns |
| 157 | - `references/macos-scenes.md` -- Settings, MenuBarExtra, WindowGroup, multi-window |
| 158 | - `references/macos-window-styling.md` -- Toolbar styles, window sizing, Commands |
| 159 | - `references/macos-views.md` -- HSplitView, Table, PasteButton, AppKit interop |
| 160 | - `references/previews.md` -- `#Preview` macro, `@Previewable` (iOS 18+), preview traits, mock data patterns for self-contained previews |
| 161 | - `references/text-patterns.md` -- Text initializer selection, verbatim vs localized |
| 162 | - `references/localization.md` -- String Catalogs, `#bundle` for packages, `LocalizedStringResource`, locale-aware formatting, RTL layout, translator comments |
| 163 | - `references/soft-deprecation.md` -- How to behave with soft-deprecated APIs (when to migrate, scoping rule, don't migrate during unrelated edits) |
| 164 | - `references/trace-analysis.md` -- Parse Instruments `.trace` files via `scripts/analyze_trace.py`; interpret main-thread coverage, high-severity SwiftUI updates, hitch narratives, and map findings back to source files |
| 165 | - `references/trace-recording.md` -- Record a new trace via `scripts/record_trace.py`: attach to a running app, launch one fresh, or capture a manually-stopped session; supports stop-file for agent-driven flows |