SwiftUI-Agent-Skill swiftui-expert-skill
Write, review, or improve SwiftUI code following best practices for state management, view composition, performance, macOS-specific APIs, and iOS 26+ Liquid Glass adoption. Use when building new SwiftUI features, refactoring existing views, reviewing code quality, or adopting modern SwiftUI patterns. Also triggers whenever an Xcode Instruments `.trace` file is referenced (to analyse it) or the user asks to **record** a new trace — attach to a running app, launch one fresh, or capture a manually-stopped session with the bundled `record_trace.py`. A target SwiftUI source file is optional; if provided it grounds recommendations in specific lines, but a trace alone is enough to diagnose hangs, hitches, CPU hotspots, and high-severity SwiftUI updates.
git clone https://github.com/AvdLee/SwiftUI-Agent-Skill
T=$(mktemp -d) && git clone --depth=1 https://github.com/AvdLee/SwiftUI-Agent-Skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/swiftui-expert-skill" ~/.claude/skills/avdlee-swiftui-agent-skill-swiftui-expert-skill && rm -rf "$T"
swiftui-expert-skill/SKILL.mdSwiftUI Expert Skill
Operating Rules
- Consult
at the start of every task to avoid deprecated APIsreferences/latest-apis.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
gating with sensible fallbacks for version-specific APIs#available
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 - Run the Topic Router below for each relevant topic
- Validate
gating and fallback paths for iOS 26+ features#available
Improve existing SwiftUI code
- Audit current implementation against the Topic Router topics
- Replace deprecated APIs with modern equivalents from
references/latest-apis.md - Refactor hot paths to reduce unnecessary state updates
- Extract complex view bodies into separate subviews
- Suggest image downsampling when
is encountered (optional optimization, seeUIImage(data:)
)references/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
for all tappable elements; add accessibility grouping and labelsButton - Gate version-specific APIs with
and provide fallbacks#available
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
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 toSwiftUI
in that case (still gives Time Profiler + Hangs + Animation Hitches). Always check--template "Time Profiler"
:--list-devices
kind →simulators
;Time Profiler
kind (real devices and the host Mac) → defaultdevices
. Full decision table inSwiftUI
.references/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,
. The script cleanly SIGINTs xctrace and waits up to 60s for finalisation.touch /tmp/stop-trace - Analyse the resulting trace (flow into the "Trace-driven improvement" workflow below).
Trace-driven improvement (Instruments .trace
provided)
.traceTrigger 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"
to scope discovery. Pick the--window START_MS:END_MS
(for logs) ortime_ms
/start_ms
(for signposts) that match the user's description. Build a window likeend_ms
.--window 10400:11700 - Run the main analysis (with or without
):--windowpython3 "${SKILL_DIR}/scripts/analyze_trace.py" --trace <path> \ --json-only --top 10 [--window START_MS:END_MS] - Interpret with
— key diagnostics:references/trace-analysis.md
inside each correlation (<25% = blocked; ≥75% = CPU-bound).main_running_coverage_pct
reveals why updates keep happening — high-edge-count sources likeswiftui-causes.top_sources
or wideUserDefaultObserver.send()
entries are structural invalidation bugs. Fixing one often collapses many downstream hot views.EnvironmentWriter
- When a specific view shows as expensive, ask who's invalidating it. Use
to get the ranked list of source nodes driving the updates.--fanin-for "<view name>" - 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 | |
| View composition | |
| Performance | |
| Lists and ForEach | |
| Layout | |
| Sheets and navigation | |
| ScrollView | |
| Focus management | |
| Animations (basics) | |
| Animations (transitions) | |
| Animations (advanced) | |
| Accessibility | |
| Swift Charts | |
| Charts accessibility | |
| Image optimization | |
| Liquid Glass (iOS 26+) | |
| macOS scenes | |
| macOS window styling | |
| macOS views | |
| Text patterns | |
| Deprecated API lookup | |
| Instruments trace analysis | |
| Instruments trace recording | |
Correctness Checklist
These are hard rules -- violations are always bugs:
-
properties are@Stateprivate -
only where a child modifies parent state@Binding - Passed values never declared as
or@State
(they ignore updates)@StateObject -
for view-owned objects;@StateObject
for injected@ObservedObject - iOS 17+:
with@State
;@Observable
for injected observables needing bindings@Bindable -
uses stable identity (neverForEach
for dynamic content).indices - Constant number of views per
elementForEach -
always includes the.animation(_:value:)
parametervalue -
properties are@FocusStateprivate - No redundant
writes inside tap gesture handlers on@FocusState
views.focusable() - iOS 26+ APIs gated with
and fallback provided#available -
present in files using chart typesimport Charts
References
-- Read first for every task. Deprecated-to-modern API transitions (iOS 15+ through iOS 26+)references/latest-apis.md
-- Property wrappers, data flow,references/state-management.md
migration@Observable
-- View extraction, container patterns,references/view-structure.md@ViewBuilder
-- Hot-path optimization, update control,references/performance-patterns.md_logChanges()
-- ForEach identity, Table (iOS 16+), inline filtering pitfallsreferences/list-patterns.md
-- Layout patterns, GeometryReader alternativesreferences/layout-best-practices.md
-- VoiceOver, Dynamic Type, grouping, traitsreferences/accessibility-patterns.md
-- Implicit/explicit animations, timing, performancereferences/animation-basics.md
-- View transitions,references/animation-transitions.md
,matchedGeometryEffectAnimatable
-- Phase/keyframe animations (iOS 17+),references/animation-advanced.md
macro (iOS 26+)@Animatable
-- Swift Charts marks, axes, selection, styling, Chart3D (iOS 26+)references/charts.md
-- Charts VoiceOver, Audio Graph, fallback strategiesreferences/charts-accessibility.md
-- Sheets, NavigationSplitView, Inspectorreferences/sheet-navigation-patterns.md
-- ScrollViewReader, programmatic scrollingreferences/scroll-patterns.md
-- Focus state, focusable views, focused values, default focus, common pitfallsreferences/focus-patterns.md
-- AsyncImage, downsampling, cachingreferences/image-optimization.md
-- iOS 26+ Liquid Glass effects and fallback patternsreferences/liquid-glass.md
-- Settings, MenuBarExtra, WindowGroup, multi-windowreferences/macos-scenes.md
-- Toolbar styles, window sizing, Commandsreferences/macos-window-styling.md
-- HSplitView, Table, PasteButton, AppKit interopreferences/macos-views.md
-- Text initializer selection, verbatim vs localizedreferences/text-patterns.md
-- Parse Instrumentsreferences/trace-analysis.md
files via.trace
; interpret main-thread coverage, high-severity SwiftUI updates, hitch narratives, and map findings back to source filesscripts/analyze_trace.py
-- Record a new trace viareferences/trace-recording.md
: attach to a running app, launch one fresh, or capture a manually-stopped session; supports stop-file for agent-driven flowsscripts/record_trace.py