Swiftui expert skill
Use when writing, reviewing, or refactoring SwiftUI code for iOS or macOS, including state and `@Observable` data flow, view composition, resizable layouts, safe areas, display scale, performance, lists, environment, localization, animation, Liquid Glass, and API migration.
by AvdLee·MIT license·★ 3,647 Stars on the repo·GitHub ↗
npx degit AvdLee/SwiftUI-Agent-Skill/skills/swiftui-expert-skill#main ~/.claude/skills/swiftui-expert-skillChecked ·commit main
Files of Swiftui expert skill
Show the full text155 lines
SwiftUI Expert Skill
Operating Rules
- Treat each
Viewtype as an invalidation boundary: give it only the data it reads and keep frequently changing dependencies close to the smallest affected subtree - Search
references/latest-apis.mdwhen writing, reviewing, or migrating API usage; look up only the APIs relevant to the task - Replace hard-deprecated APIs with modern equivalents. During feature work, flag soft-deprecated APIs and leave them in place (see
references/soft-deprecation.md) - Prefer native SwiftUI APIs over UIKit/AppKit bridging unless bridging is necessary
- Focus on correctness and performance; do not enforce specific architectures (MVVM, VIPER, etc.)
- Encourage separating business logic from views for testability without mandating how
- Follow Apple's Human Interface Guidelines and API design patterns
- Only adopt Liquid Glass when explicitly requested by the user (see
references/liquid-glass.md) - Present performance optimizations as suggestions, not requirements
- Use
#availablegating with sensible fallbacks for version-specific APIs - For layout and rendering inputs, read the value nearest the SwiftUI view that consumes it; do not substitute process-global screen state
Task Workflow
Review existing SwiftUI code
- Read the code under review and identify which topics apply
- Flag deprecated APIs (compare against
references/latest-apis.md); replace hard-deprecated APIs, and flag soft-deprecated APIs without rewriting them unless the user asked to migrate - Run the Topic Router below for each relevant topic
- Validate
#availablegating and fallback paths for version-specific features - For broad codebase reviews, first identify smaller focus areas and present them one at a time; if the user requests a whole-codebase review, divide it into a TODO list
Improve existing SwiftUI code
- Audit current implementation against the Topic Router topics
- Replace hard-deprecated APIs with modern equivalents from
references/latest-apis.md; flag soft-deprecated APIs and do not rewrite them during feature work - Refactor hot paths to reduce unnecessary state updates
- Extract complex view bodies into separate subviews
- Suggest image downsampling when
UIImage(data:)is encountered (optional optimization, seereferences/image-optimization.md)
Implement new SwiftUI feature
- Design data flow first: identify owned vs injected state
- Structure views for optimal diffing (extract subviews early)
- Apply correct animation patterns (implicit vs explicit, transitions)
- Use
Buttonfor all tappable elements; add accessibility grouping and labels - Gate version-specific APIs with
#availableand provide fallbacks
Record a new Instruments trace
Trigger when the user asks to "record a trace", "profile the app", "capture a session", etc. Full reference: references/trace-recording.md.
- 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:
python3 "${SKILL_DIR}/scripts/record_trace.py" --list-devices - Pick a template based on target kind — the
SwiftUItemplate 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:simulatorskind →Time Profiler;deviceskind (real devices and the host Mac) → defaultSwiftUI. Full decision table inreferences/trace-recording.md. - 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:
For interactive sessions, just tell the user to press Ctrl+C when done.python3 "${SKILL_DIR}/scripts/record_trace.py" \ --device "<name|udid>" --attach "<AppName>" \ --stop-file /tmp/stop-trace --output ~/Desktop/session.trace - 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. - Analyse the resulting trace (flow into the "Trace-driven improvement" workflow below).
Trace-driven improvement (Instruments .trace provided)
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.
Full reference: references/trace-analysis.md. Summary of the composition pattern:
- Scope the analysis. Ask yourself: does the user want the whole trace, or a slice?
- "focus on X / after X / between X and Y / during X" → resolve to a window first (see step 2).
- No scoping cue → analyse the whole trace.
- Resolve a window (only if the user scoped). The parser exposes two discovery modes:
Both modes accept# Find a log that marks the start/end of the region of interest: python3 "${SKILL_DIR}/scripts/analyze_trace.py" --trace <path> \ --list-logs --log-message-contains "loaded feed" --log-limit 5 # Or list os_signpost intervals (paired begin/end), filterable by name: python3 "${SKILL_DIR}/scripts/analyze_trace.py" --trace <path> \ --list-signposts --signpost-name-contains "ImageDecode"--window START_MS:END_MSto scope discovery. Pick thetime_ms(for logs) orstart_ms/end_ms(for signposts) that match the user's description. Build a window like--window 10400:11700. - Run the main analysis (with or without
--window):python3 "${SKILL_DIR}/scripts/analyze_trace.py" --trace <path> \ --json-only --top 10 [--window START_MS:END_MS] - Interpret with
references/trace-analysis.md— key diagnostics:main_running_coverage_pctinside each correlation (<25% = blocked; ≥75% = CPU-bound).swiftui-causes.top_sourcesreveals why updates keep happening — high-edge-count sources likeUserDefaultObserver.send()or wideEnvironmentWriterentries are structural invalidation bugs. Fixing one often collapses many downstream hot views.
- 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. - 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.
- 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.
- Only edit code if the user asked for edits.
Topic Router
Consult the reference file for each topic relevant to the current task:
| Topic | Reference |
|---|---|
| State management | references/state-management.md |
Environment and @Entry |
references/environment-patterns.md |
| View composition | references/view-structure.md |
| View modifiers and identity | references/modifier-patterns.md |
| Performance | references/performance-patterns.md |
| Lists and ForEach | references/list-patterns.md |
| Resizable layout, safe areas, two-column reflow, foldable grids, arrangements, and reserved regions | references/layout-best-practices.md |
| iPhone Duo, foldable, or large-display screens (read first to choose the technique) | references/iphone-duo.md |
Sheets, navigation, NavigationSplitView on large displays, and tab bar/sidebar (sidebarAdaptable) |
references/sheet-navigation-patterns.md |
| ScrollView, scroll position, and scroll geometry | references/scroll-patterns.md |
| Focus management | references/focus-patterns.md |
| Animations (basics) | references/animation-basics.md |
| Animations (transitions) | references/animation-transitions.md |
| Animations (advanced) | references/animation-advanced.md |
| Accessibility | references/accessibility-patterns.md |
| Swift Charts | references/charts.md |
| Charts accessibility | references/charts-accessibility.md |
| Image optimization and display scale | references/image-optimization.md |
| Toolbars | references/toolbar-patterns.md |
| Document-based apps | references/document-apps.md |
| WebKit | references/webkit-integration.md |
| Styled text editing | references/styled-text-editing.md |
| Liquid Glass (iOS 26+) | references/liquid-glass.md |
| macOS scenes | references/macos-scenes.md |
| macOS window styling | references/macos-window-styling.md |
| macOS views | references/macos-views.md |
| Text patterns | references/text-patterns.md |
| Localization | references/localization.md |
| Deprecated API lookup | references/latest-apis.md |
| Handling soft-deprecated APIs | references/soft-deprecation.md |
| Previews | references/previews.md |
| Instruments trace analysis | references/trace-analysis.md |
| Instruments trace recording | references/trace-recording.md |
Correctness Checklist
These are hard rules -- violations are always bugs:
-
@Stateproperties areprivate -
@Bindingonly where a child modifies parent state - Changing parent-owned inputs are not stored as
@State/@StateObject; intentional state seeds are documented as one-time -
@StateObjectfor view-owned objects;@ObservedObjectfor injected - iOS 17+:
@Statewith@Observable;@Bindablefor injected observables needing bindings -
ForEachuses stable identity (never.indices/\.offset; id outlives the view and isn't derived from mutable content) - Constant number of views per
ForEachelement;Listrows are unary - No closures stored in custom
@Environment/@FocusedValuekeys - Custom
@Entrydefault values are stable (noModel()/Date()/UUID()expressions) - SwiftUI display scale comes from
@Environment(\.displayScale), not global screen state - Safe-area content does not double-apply
GeometryProxy.safeAreaInsets -
.animation(_:value:)always includes thevalueparameter -
@FocusStateproperties areprivate - No redundant
@FocusStatewrites inside tap gesture handlers on.focusable()views - Version-specific APIs are gated with
#availableand have sensible fallbacks -
import Chartspresent in files using chart types - Previews use self-contained mock data; no dependency on live services or network
| 1 | |
| 2 | name swiftui-expert-skill |
| 3 | description Use when writing, reviewing, or refactoring SwiftUI code for iOS or macOS, including state and `@Observable` data flow, view composition, resizable layouts, safe areas, display scale, performance, lists, environment, localization, animation, Liquid Glass, and API migration. Also use for iPhone Duo, foldable, or large-display layouts (`NavigationSplitView` on large displays, iPhone tab sidebar, two-column reflow, foldable grids, `ArrangementView`, `ReservedRegion`), hinge effects, vertical bars, `@State` initialization or synthesized-property diagnostics, `@ContentBuilder` ambiguity, `reorderable` drag/drop, custom `AsyncImage` `URLSession`, swipe actions outside List, item-bound `alert`/`confirmationDialog`, `ToolbarOverflowMenu`, `AnimatableValues`, Document APIs (`Document`/`DocumentReader`), and Instruments `.trace` capture or analysis. |
| 4 | |
| 5 | |
| 6 | # SwiftUI Expert Skill |
| 7 | |
| 8 | ## Operating Rules |
| 9 | |
| 10 | Treat each `View` type as an invalidation boundary: give it only the data it reads and keep frequently changing dependencies close to the smallest affected subtree |
| 11 | Search `references/latest-apis.md` when writing, reviewing, or migrating API usage; look up only the APIs relevant to the task |
| 12 | Replace hard-deprecated APIs with modern equivalents. During feature work, flag soft-deprecated APIs and leave them in place (see `references/soft-deprecation.md`) |
| 13 | Prefer native SwiftUI APIs over UIKit/AppKit bridging unless bridging is necessary |
| 14 | Focus on correctness and performance; do not enforce specific architectures (MVVM, VIPER, etc.) |
| 15 | Encourage separating business logic from views for testability without mandating how |
| 16 | Follow Apple's Human Interface Guidelines and API design patterns |
| 17 | Only adopt Liquid Glass when explicitly requested by the user (see `references/liquid-glass.md`) |
| 18 | Present performance optimizations as suggestions, not requirements |
| 19 | Use `#available` gating with sensible fallbacks for version-specific APIs |
| 20 | For layout and rendering inputs, read the value nearest the SwiftUI view that consumes it; do not substitute process-global screen state |
| 21 | |
| 22 | ## Task Workflow |
| 23 | |
| 24 | ### Review existing SwiftUI code |
| 25 | Read the code under review and identify which topics apply |
| 26 | Flag deprecated APIs (compare against `references/latest-apis.md`); replace hard-deprecated APIs, and flag soft-deprecated APIs without rewriting them unless the user asked to migrate |
| 27 | Run the Topic Router below for each relevant topic |
| 28 | Validate `#available` gating and fallback paths for version-specific features |
| 29 | For broad codebase reviews, first identify smaller focus areas and present them one at a time; if the user requests a whole-codebase review, divide it into a TODO list |
| 30 | |
| 31 | ### Improve existing SwiftUI code |
| 32 | Audit current implementation against the Topic Router topics |
| 33 | Replace hard-deprecated APIs with modern equivalents from `references/latest-apis.md`; flag soft-deprecated APIs and do not rewrite them during feature work |
| 34 | Refactor hot paths to reduce unnecessary state updates |
| 35 | Extract complex view bodies into separate subviews |
| 36 | Suggest image downsampling when `UIImage(data:)` is encountered (optional optimization, see `references/image-optimization.md`) |
| 37 | |
| 38 | ### Implement new SwiftUI feature |
| 39 | Design data flow first: identify owned vs injected state |
| 40 | Structure views for optimal diffing (extract subviews early) |
| 41 | Apply correct animation patterns (implicit vs explicit, transitions) |
| 42 | Use `Button` for all tappable elements; add accessibility grouping and labels |
| 43 | Gate version-specific APIs with `#available` and provide fallbacks |
| 44 | |
| 45 | ### Record a new Instruments trace |
| 46 | Trigger when the user asks to "record a trace", "profile the app", "capture a session", etc. Full reference: `references/trace-recording.md`. |
| 47 | |
| 48 | **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: |
| 49 | |
| 50 | python3 "${SKILL_DIR}/scripts/record_trace.py" --list-devices |
| 51 | |
| 52 | **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`. |
| 53 | **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: |
| 54 | |
| 55 | python3 "${SKILL_DIR}/scripts/record_trace.py" \ |
| 56 | --device "<name|udid>" --attach "<AppName>" \ |
| 57 | --stop-file /tmp/stop-trace --output ~/Desktop/session.trace |
| 58 | |
| 59 | For interactive sessions, just tell the user to press Ctrl+C when done. |
| 60 | **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. |
| 61 | **Analyse** the resulting trace (flow into the "Trace-driven improvement" workflow below). |
| 62 | |
| 63 | ### Trace-driven improvement (Instruments `.trace` provided) |
| 64 | 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. |
| 65 | |
| 66 | Full reference: `references/trace-analysis.md`. Summary of the composition pattern: |
| 67 | |
| 68 | **Scope the analysis.** Ask yourself: does the user want the whole trace, or a slice? |
| 69 | "focus on X / after X / between X and Y / during X" → **resolve to a window first** (see step 2). |
| 70 | No scoping cue → analyse the whole trace. |
| 71 | **Resolve a window (only if the user scoped).** The parser exposes two discovery modes: |
| 72 | |
| 73 | # Find a log that marks the start/end of the region of interest: |
| 74 | python3 "${SKILL_DIR}/scripts/analyze_trace.py" --trace <path> \ |
| 75 | --list-logs --log-message-contains "loaded feed" --log-limit 5 |
| 76 | # Or list os_signpost intervals (paired begin/end), filterable by name: |
| 77 | python3 "${SKILL_DIR}/scripts/analyze_trace.py" --trace <path> \ |
| 78 | --list-signposts --signpost-name-contains "ImageDecode" |
| 79 | |
| 80 | 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`. |
| 81 | **Run the main analysis** (with or without `--window`): |
| 82 | |
| 83 | python3 "${SKILL_DIR}/scripts/analyze_trace.py" --trace <path> \ |
| 84 | --json-only --top 10 [--window START_MS:END_MS] |
| 85 | |
| 86 | **Interpret with `references/trace-analysis.md`** — key diagnostics: |
| 87 | `main_running_coverage_pct` inside each correlation (<25% = blocked; ≥75% = CPU-bound). |
| 88 | `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. |
| 89 | **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. |
| 90 | **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. |
| 91 | **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. |
| 92 | Only edit code if the user asked for edits. |
| 93 | |
| 94 | ### Topic Router |
| 95 | |
| 96 | Consult the reference file for each topic relevant to the current task: |
| 97 | |
| 98 | | Topic | Reference | |
| 99 | |-------|-----------| |
| 100 | | State management | `references/state-management.md` | |
| 101 | | Environment and `@Entry` | `references/environment-patterns.md` | |
| 102 | | View composition | `references/view-structure.md` | |
| 103 | | View modifiers and identity | `references/modifier-patterns.md` | |
| 104 | | Performance | `references/performance-patterns.md` | |
| 105 | | Lists and ForEach | `references/list-patterns.md` | |
| 106 | | Resizable layout, safe areas, two-column reflow, foldable grids, arrangements, and reserved regions | `references/layout-best-practices.md` | |
| 107 | | iPhone Duo, foldable, or large-display screens (read first to choose the technique) | `references/iphone-duo.md` | |
| 108 | | Sheets, navigation, `NavigationSplitView` on large displays, and tab bar/sidebar (`sidebarAdaptable`) | `references/sheet-navigation-patterns.md` | |
| 109 | | ScrollView, scroll position, and scroll geometry | `references/scroll-patterns.md` | |
| 110 | | Focus management | `references/focus-patterns.md` | |
| 111 | | Animations (basics) | `references/animation-basics.md` | |
| 112 | | Animations (transitions) | `references/animation-transitions.md` | |
| 113 | | Animations (advanced) | `references/animation-advanced.md` | |
| 114 | | Accessibility | `references/accessibility-patterns.md` | |
| 115 | | Swift Charts | `references/charts.md` | |
| 116 | | Charts accessibility | `references/charts-accessibility.md` | |
| 117 | | Image optimization and display scale | `references/image-optimization.md` | |
| 118 | | Toolbars | `references/toolbar-patterns.md` | |
| 119 | | Document-based apps | `references/document-apps.md` | |
| 120 | | WebKit | `references/webkit-integration.md` | |
| 121 | | Styled text editing | `references/styled-text-editing.md` | |
| 122 | | Liquid Glass (iOS 26+) | `references/liquid-glass.md` | |
| 123 | | macOS scenes | `references/macos-scenes.md` | |
| 124 | | macOS window styling | `references/macos-window-styling.md` | |
| 125 | | macOS views | `references/macos-views.md` | |
| 126 | | Text patterns | `references/text-patterns.md` | |
| 127 | | Localization | `references/localization.md` | |
| 128 | | Deprecated API lookup | `references/latest-apis.md` | |
| 129 | | Handling soft-deprecated APIs | `references/soft-deprecation.md` | |
| 130 | | Previews | `references/previews.md` | |
| 131 | | Instruments trace analysis | `references/trace-analysis.md` | |
| 132 | | Instruments trace recording | `references/trace-recording.md` | |
| 133 | |
| 134 | ## Correctness Checklist |
| 135 | |
| 136 | These are hard rules -- violations are always bugs: |
| 137 | |
| 138 | [ ] `@State` properties are `private` |
| 139 | [ ] `@Binding` only where a child modifies parent state |
| 140 | [ ] Changing parent-owned inputs are not stored as `@State`/`@StateObject`; intentional state seeds are documented as one-time |
| 141 | [ ] `@StateObject` for view-owned objects; `@ObservedObject` for injected |
| 142 | [ ] iOS 17+: `@State` with `@Observable`; `@Bindable` for injected observables needing bindings |
| 143 | [ ] `ForEach` uses stable identity (never `.indices`/`\.offset`; id outlives the view and isn't derived from mutable content) |
| 144 | [ ] Constant number of views per `ForEach` element; `List` rows are unary |
| 145 | [ ] No closures stored in custom `@Environment`/`@FocusedValue` keys |
| 146 | [ ] Custom `@Entry` default values are stable (no `Model()`/`Date()`/`UUID()` expressions) |
| 147 | [ ] SwiftUI display scale comes from `@Environment(\.displayScale)`, not global screen state |
| 148 | [ ] Safe-area content does not double-apply `GeometryProxy.safeAreaInsets` |
| 149 | [ ] `.animation(_:value:)` always includes the `value` parameter |
| 150 | [ ] `@FocusState` properties are `private` |
| 151 | [ ] No redundant `@FocusState` writes inside tap gesture handlers on `.focusable()` views |
| 152 | [ ] Version-specific APIs are gated with `#available` and have sensible fallbacks |
| 153 | [ ] `import Charts` present in files using chart types |
| 154 | [ ] Previews use self-contained mock data; no dependency on live services or network |
| 155 |
Discussion
Browse more free Claude skills.