SwiftUI Expert Skill
When to Use
- You are building, reviewing, or refactoring SwiftUI code and need current best practices.
- The task involves state management, view composition, performance, accessibility, or iOS 26+ Liquid Glass adoption.
- You need a fact-based SwiftUI guidance layer without locking into a specific application architecture.
Overview
Use this skill to build, review, or improve SwiftUI features with correct state management, 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
- First, consult
references/latest-apis.mdto ensure only current, non-deprecated APIs are used - Check property wrapper usage against the selection guide (see
references/state-management.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) - Check animation patterns for correctness (see
references/animation-basics.md,references/animation-transitions.md) - Review accessibility: proper grouping, traits, Dynamic Type support (see
references/accessibility-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
- First, consult
references/latest-apis.mdto replace any deprecated APIs with their modern equivalents - Audit state management for correct wrapper selection (see
references/state-management.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) - Improve animation patterns (use value parameter, proper transitions, see
references/animation-basics.md,references/animation-transitions.md) - Improve accessibility: use
Buttonover tap gestures, add@ScaledMetricfor Dynamic Type (seereferences/accessibility-patterns.md) - Suggest image downsampling when
UIImage(data:)is used (as optional optimization, seereferences/image-optimization.md) - Adopt Liquid Glass only when explicitly requested by the user
3) Implement new SwiftUI feature
- First, consult
references/latest-apis.mdto use only current, non-deprecated APIs for the target deployment version - Design data flow first: identify owned vs injected state (see
references/state-management.md) - Structure views for optimal diffing (extract subviews early, see
references/view-structure.md) - Keep business logic in services and models for testability (see
references/layout-best-practices.md) - Use correct animation patterns (implicit vs explicit, transitions, see
references/animation-basics.md,references/animation-transitions.md,references/animation-advanced.md) - Use
Buttonfor tappable elements, add accessibility grouping and labels (seereferences/accessibility-patterns.md) - Apply glass effects after layout/appearance modifiers (see
references/liquid-glass.md) - Gate iOS 26+ features with
#availableand provide fallbacks
Core Guidelines
State Management
@Statemust beprivate; use for internal view state@Bindingonly when a child needs to modify parent state@StateObjectwhen view creates the object;@ObservedObjectwhen injected- iOS 17+: Use
@Statewith@Observableclasses; use@Bindablefor injected observables needing bindings - Use
letfor read-only values;var+.onChange()for reactive reads - Never pass values into
@Stateor@StateObject— they only accept initial values - Nested
ObservableObjectdoesn't propagate changes — pass nested objects directly;@Observablehandles nesting fine
View Composition
- Extract complex views into separate subviews for better readability and performance
- Prefer modifiers over conditional views for state changes (maintains view identity)
- Keep view
bodysimple and pure (no side effects or complex logic) - Use
@ViewBuilderfunctions only for small, simple sections - Prefer
@ViewBuilder let content: Contentover closure-based content properties - Keep business logic in services and models; views should orchestrate UI flow
- Action handlers should reference methods, not contain inline logic
- 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/LazyHStackfor large lists - Use stable identity for
ForEach(never.indicesfor dynamic content) - Ensure constant number of views per
ForEachelement - Avoid inline filtering in
ForEach(prefilter and cache) - Avoid
AnyViewin 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._logChanges()orSelf._printChanges()to debug unexpected view updates
Animations
- Use
.animation(_:value:)with value parameter (deprecated version without value is too broad) - Use
withAnimationfor event-driven animations (button taps, gestures) - Prefer transforms (
offset,scale,rotation) over layout changes (frame) for performance - Transitions require animations outside the conditional structure
- Custom
Animatableimplementations must have explicitanimatableData - Use
.phaseAnimatorfor multi-step sequences (iOS 17+) - Use
.keyframeAnimatorfor precise timing control (iOS 17+) - Animation completion handlers need
.transaction(value:)for reexecution - Implicit animations override explicit animations (later in view tree wins)
Accessibility
- Prefer
ButtonoveronTapGesturefor tappable elements (free VoiceOver support) - Use
@ScaledMetricfor custom numeric values that should scale with Dynamic Type - Group related elements with
accessibilityElement(children: .combine)for joined labels - Provide
accessibilityLabelwhen default labels are unclear or missing - Use
accessibilityRepresentationfor custom controls that should behave like native ones
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
glassEffectIDwith@Namespacefor morphing transitions
Quick Reference
Property Wrapper Selection
| Wrapper | Use When |
|---|---|
@State | Internal view state (must be private) |
@Binding | Child modifies parent's state |
@StateObject | View owns an ObservableObject |
@ObservedObject | View receives an ObservableObject |
@Bindable | iOS 17+: Injected @Observable needing bindings |
let | Read-only value from parent |
var | Read-only value watched via .onChange() |
Liquid Glass Patterns
// Basic glass effect with fallback
if #available(iOS 26, *) {
content
.padding()
.glassEffect(.regular.interactive(), in: .rect(cornerRadi