SwiftUI Expert Skill
Overview
Use 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.
Workflow Decision Tree
1) Review existing SwiftUI code
- - Check property wrapper usage against the selection guide (see
references/state-management.md) - Verify modern API usage (see
references/modern-apis.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) - 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
- - Audit state management for correct wrapper selection (prefer
@Observable over ObservableObject) - Replace deprecated APIs with modern equivalents (see
references/modern-apis.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) - Suggest image downsampling when
UIImage(data:) is used (as optional optimization, see references/image-optimization.md) - Adopt Liquid Glass only when explicitly requested by the user
3) Implement new SwiftUI feature
- - Design data flow first: identify owned vs injected state (see
references/state-management.md) - Use modern APIs (no deprecated modifiers or patterns, see
references/modern-apis.md) - Use
@Observable for shared state (with @MainActor if not using default actor isolation) - Structure views for optimal diffing (extract subviews early, keep views small, see
references/view-structure.md) - Separate business logic into testable models (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) - Apply glass effects after layout/appearance modifiers (see
references/liquid-glass.md) - Gate iOS 26+ features with
#available and provide fallbacks
Core Guidelines
State Management
- - Always prefer
@Observable over ObservableObject for new code - Mark
@Observable classes with @MainActor unless using default actor isolation - Always mark
@State and @StateObject as private (makes dependencies clear) - Never declare passed values as
@State or @StateObject (they only accept initial values) - Use
@State with @Observable classes (not @StateObject) - INLINECODE41 only when child needs to modify parent state
- INLINECODE42 for injected
@Observable objects needing bindings - Use
let for read-only values; var + .onChange() for reactive reads - Legacy:
@StateObject for owned ObservableObject; @ObservedObject for injected - Nested
ObservableObject doesn't work (pass nested objects directly); @Observable handles nesting fine
Modern APIs
- - Use
foregroundStyle() instead of INLINECODE53 - Use
clipShape(.rect(cornerRadius:)) instead of INLINECODE55 - Use
Tab API instead of INLINECODE57 - Use
Button instead of onTapGesture() (unless need location/count) - Use
NavigationStack instead of INLINECODE61 - Use
navigationDestination(for:) for type-safe navigation - Use two-parameter or no-parameter
onChange() variant - Use
ImageRenderer for rendering SwiftUI views - Use
.sheet(item:) instead of .sheet(isPresented:) for model-based content - Sheets should own their actions and call
dismiss() internally - Use
ScrollViewReader for programmatic scrolling with stable IDs - Avoid
UIScreen.main.bounds for sizing - Avoid
GeometryReader when alternatives exist (e.g., containerRelativeFrame())
Swift Best Practices
- - Use modern Text formatting (
.format parameters, not String(format:)) - Use
localizedStandardContains() for user-input filtering (not contains()) - Prefer static member lookup (
.blue vs Color.blue) - Use
.task modifier for automatic cancellation of async work - Use
.task(id:) for value-dependent tasks
View Composition
- - Prefer modifiers over conditional views for state changes (maintains view identity)
- Extract complex views into separate subviews for better readability and performance
- Keep views small for optimal performance
- Keep view
body simple and pure (no side effects or complex logic) - Use
@ViewBuilder functions only for small, simple sections - Prefer
@ViewBuilder let content: Content over closure-based content properties - Separate business logic into testable models (not about enforcing architectures)
- Action handlers should reference methods, not contain inline logic
- Use relative layout over hard-coded constants
- 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/LazyHStack for large lists - Use stable identity for
ForEach (never .indices for dynamic content) - Ensure constant number of views per
ForEach element - Avoid inline filtering in
ForEach (prefilter and cache) - Avoid
AnyView in 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._printChanges() to debug unexpected view updates
Animations
- - Use
.animation(_:value:) with value parameter (deprecated version without value is too broad) - Use
withAnimation for 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
Animatable implementations must have explicit INLINECODE102 - Use
.phaseAnimator for multi-step sequences (iOS 17+) - Use
.keyframeAnimator for precise timing control (iOS 17+) - Animation completion handlers need
.transaction(value:) for reexecution - Implicit animations override explicit animations (later in view tree wins)
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 INLINECODE108
- Apply
.glassEffect() after layout and visual modifiers - Use
.interactive() only for tappable/focusable elements - Use
glassEffectID with @Namespace for morphing transitions
Quick Reference
Property Wrapper Selection (Modern)
| Wrapper | Use When |
|---|
| INLINECODE113 | Internal view state (must be private), or owned @Observable class |
| INLINECODE116 |
Child modifies parent's state |
|
@Bindable | Injected
@Observable needing bindings |
|
let | Read-only value from parent |
|
var | Read-only value watched via
.onChange() |
Legacy (Pre-iOS 17):
| Wrapper | Use When |
|---|
| INLINECODE122 | View owns an ObservableObject (use @State with @Observable instead) |
| INLINECODE126 |
View receives an
ObservableObject |
Modern API Replacements
| Deprecated | Modern Alternative |
|---|
| INLINECODE128 | INLINECODE129 |
| INLINECODE130 |
clipShape(.rect(cornerRadius:)) |
|
tabItem() |
Tab API |
|
onTapGesture() |
Button (unless need location/count) |
|
NavigationView |
NavigationStack |
|
onChange(of:) { value in } |
onChange(of:) { old, new in } or
onChange(of:) { } |
|
fontWeight(.bold) |
bold() |
|
GeometryReader |
containerRelativeFrame() or
visualEffect() |
|
showsIndicators: false |
.scrollIndicators(.hidden) |
|
String(format: "%.2f", value) |
Text(value, format: .number.precision(.fractionLength(2))) |
|
string.contains(search) |
string.localizedStandardContains(search) (for user input) |
Liquid Glass Patterns
CODEBLOCK0
Review Checklist
State Management
- - [ ] Using
@Observable instead of ObservableObject for new code - [ ]
@Observable classes marked with @MainActor (if needed) - [ ] Using
@State with @Observable classes (not @StateObject) - [ ]
@State and @StateObject properties are INLINECODE161 - [ ] Passed values NOT declared as
@State or INLINECODE163 - [ ]
@Binding only where child modifies parent state - [ ]
@Bindable for injected @Observable needing bindings - [ ] Nested
ObservableObject avoided (or passed directly to child views)
Modern APIs (see references/modern-apis.md)
- - [ ] Using
foregroundStyle() instead of INLINECODE170 - [ ] Using
clipShape(.rect(cornerRadius:)) instead of INLINECODE172 - [ ] Using
Tab API instead of INLINECODE174 - [ ] Using
Button instead of onTapGesture() (unless need location/count) - [ ] Using
NavigationStack instead of INLINECODE178 - [ ] Avoiding INLINECODE179
- [ ] Using alternatives to
GeometryReader when possible - [ ] Button images include text labels for accessibility
Sheets & Navigation (see references/sheet-navigation-patterns.md)
- - [ ] Using
.sheet(item:) for model-based sheets - [ ] Sheets own their actions and dismiss internally
- [ ] Using
navigationDestination(for:) for type-safe navigation
ScrollView (see references/scroll-patterns.md)
- - [ ] Using
ScrollViewReader with stable IDs for programmatic scrolling - [ ] Using
.scrollIndicators(.hidden) instead of initializer parameter
Text & Formatting (see references/text-formatting.md)
- - [ ] Using modern Text formatting (not
String(format:)) - [ ] Using
localizedStandardContains() for search filtering
View Structure (see references/view-structure.md)
- - [ ] Using modifiers instead of conditionals for state changes
- [ ] Complex views extracted to separate subviews
- [ ] Views kept small for performance
- [ ] Container views use INLINECODE191
Performance (see references/performance-patterns.md)
- - [ ] View
body kept simple and pure (no side effects) - [ ] Passing only needed values (not large config objects)
- [ ] Eliminating unnecessary dependencies
- [ ] State updates check for value changes before assigning
- [ ] Hot paths minimize state updates
- [ ] No object creation in INLINECODE194
- [ ] Heavy computation moved out of INLINECODE195
List Patterns (see references/list-patterns.md)
- - [ ] ForEach uses stable identity (not
.indices) - [ ] Constant number of views per ForEach element
- [ ] No inline filtering in ForEach
- [ ] No
AnyView in list rows
Layout (see references/layout-best-practices.md)
- - [ ] Avoiding layout thrash (deep hierarchies, excessive GeometryReader)
- [ ] Gating frequent geometry updates by thresholds
- [ ] Business logic separated into testable models
- [ ] Action handlers reference methods (not inline logic)
- [ ] Using relative layout (not hard-coded constants)
- [ ] Views work in any context (context-agnostic)
Animations (see references/animation-basics.md, references/animation-transitions.md, references/animation-advanced.md)
- - [ ] Using
.animation(_:value:) with value parameter - [ ] Using
withAnimation for event-driven animations - [ ] Transitions paired with animations outside conditional structure
- [ ] Custom
Animatable has explicit animatableData implementation - [ ] Preferring transforms over layout changes for animation performance
- [ ] Phase animations for multi-step sequences (iOS 17+)
- [ ] Keyframe animations for precise timing (iOS 17+)
- [ ] Completion handlers use
.transaction(value:) for reexecution
Liquid Glass (iOS 26+)
- - [ ]
#available(iOS 26, *) with fallback for Liquid Glass - [ ] Multiple glass views wrapped in INLINECODE209
- [ ]
.glassEffect() applied after layout/appearance modifiers - [ ]
.interactive() only on user-interactable elements - [ ] Shapes and tints consistent across related elements
References
- -
references/state-management.md - Property wrappers and data flow (prefer @Observable) - INLINECODE214 - View composition, extraction, and container patterns
- INLINECODE215 - Performance optimization techniques and anti-patterns
- INLINECODE216 - ForEach identity, stability, and list best practices
- INLINECODE217 - Layout patterns, context-agnostic views, and testability
- INLINECODE218 - Modern API usage and deprecated replacements
- INLINECODE219 - Core animation concepts, implicit/explicit animations, timing, performance
- INLINECODE220 - Transitions, custom transitions, Animatable protocol
- INLINECODE221 - Transactions, phase/keyframe animations (iOS 17+), completion handlers (iOS 17+)
- INLINECODE222 - Sheet presentation and navigation patterns
- INLINECODE223 - ScrollView patterns and programmatic scrolling
- INLINECODE224 - Modern text formatting and string operations
- INLINECODE225 - AsyncImage, image downsampling, and optimization
- INLINECODE226 - iOS 26+ Liquid Glass API
Philosophy
This skill focuses on facts and best practices, not architectural opinions:
- - We don't enforce specific architectures (e.g., MVVM, VIPER)
- We do encourage separating business logic for testability
- We prioritize modern APIs over deprecated ones
- We emphasize thread safety with
@MainActor and INLINECODE228 - We optimize for performance and maintainability
- We follow Apple's Human Interface Guidelines and API design patterns
SwiftUI 专家技能
概述
使用此技能来构建、审查或改进SwiftUI功能,包括正确的状态管理、现代API使用、Swift并发最佳实践、最优视图组合以及iOS 26+ Liquid Glass样式。优先使用原生API、Apple设计指南和注重性能的模式。本技能侧重于事实和最佳实践,不强制特定的架构模式。
工作流决策树
1) 审查现有SwiftUI代码
- - 根据选择指南检查属性包装器的使用(参见 references/state-management.md)
- 验证现代API的使用(参见 references/modern-apis.md)
- 验证视图组合是否遵循提取规则(参见 references/view-structure.md)
- 检查是否应用了性能模式(参见 references/performance-patterns.md)
- 验证列表模式是否使用稳定标识(参见 references/list-patterns.md)
- 检查动画模式的正确性(参见 references/animation-basics.md、references/animation-transitions.md)
- 检查Liquid Glass使用的正确性和一致性(参见 references/liquid-glass.md)
- 验证iOS 26+可用性处理,并提供合理的回退方案
2) 改进现有SwiftUI代码
- - 审计状态管理,确保选择正确的包装器(优先使用@Observable而非ObservableObject)
- 用现代等效API替换已弃用的API(参见 references/modern-apis.md)
- 将复杂视图提取为单独的子视图(参见 references/view-structure.md)
- 重构热路径以最小化冗余状态更新(参见 references/performance-patterns.md)
- 确保ForEach使用稳定标识(参见 references/list-patterns.md)
- 改进动画模式(使用value参数、适当的过渡,参见 references/animation-basics.md、references/animation-transitions.md)
- 当使用UIImage(data:)时建议图像降采样(作为可选优化,参见 references/image-optimization.md)
- 仅在用户明确要求时才采用Liquid Glass
3) 实现新的SwiftUI功能
- - 首先设计数据流:识别自有状态与注入状态(参见 references/state-management.md)
- 使用现代API(不使用已弃用的修饰符或模式,参见 references/modern-apis.md)
- 对共享状态使用@Observable(如果不使用默认的Actor隔离,则配合@MainActor)
- 为最优差异比较构建视图(尽早提取子视图,保持视图小巧,参见 references/view-structure.md)
- 将业务逻辑分离到可测试的模型中(参见 references/layout-best-practices.md)
- 使用正确的动画模式(隐式与显式、过渡,参见 references/animation-basics.md、references/animation-transitions.md、references/animation-advanced.md)
- 在布局/外观修饰符之后应用玻璃效果(参见 references/liquid-glass.md)
- 使用#available限制iOS 26+功能,并提供回退方案
核心指南
状态管理
- - 新代码始终优先使用@Observable而非ObservableObject
- 除非使用默认的Actor隔离,否则将@Observable类标记为@MainActor
- 始终将@State和@StateObject标记为private(使依赖关系清晰)
- 切勿将传入值声明为@State或@StateObject(它们只接受初始值)
- 对@Observable类使用@State(而非@StateObject)
- 仅在子视图需要修改父视图状态时使用@Binding
- 对需要绑定的注入@Observable对象使用@Bindable
- 只读值使用let;响应式读取使用var + .onChange()
- 传统方式:自有ObservableObject使用@StateObject;注入的使用@ObservedObject
- 嵌套的ObservableObject不起作用(需直接传递嵌套对象);@Observable可以很好地处理嵌套
现代API
- - 使用foregroundStyle()替代foregroundColor()
- 使用clipShape(.rect(cornerRadius:))替代cornerRadius()
- 使用Tab API替代tabItem()
- 使用Button替代onTapGesture()(除非需要位置/计数)
- 使用NavigationStack替代NavigationView
- 使用navigationDestination(for:)进行类型安全导航
- 使用双参数或无参数的onChange()变体
- 使用ImageRenderer渲染SwiftUI视图
- 对基于模型的内容使用.sheet(item:)替代.sheet(isPresented:)
- 表单应拥有自己的操作并在内部调用dismiss()
- 使用ScrollViewReader进行带稳定ID的程序化滚动
- 避免使用UIScreen.main.bounds进行尺寸计算
- 当存在替代方案时避免使用GeometryReader(例如containerRelativeFrame())
Swift最佳实践
- - 使用现代Text格式化(.format参数,而非String(format:))
- 对用户输入过滤使用localizedStandardContains()(而非contains())
- 优先使用静态成员查找(.blue vs Color.blue)
- 使用.task修饰符实现异步工作的自动取消
- 对值依赖的任务使用.task(id:)
视图组合
- - 对状态变化优先使用修饰符而非条件视图(保持视图标识)
- 将复杂视图提取为单独的子视图,以提高可读性和性能
- 保持视图小巧以获得最佳性能
- 保持视图body简单纯净(无副作用或复杂逻辑)
- 仅对小型简单部分使用@ViewBuilder函数
- 优先使用@ViewBuilder let content: Content而非基于闭包的内容属性
- 将业务逻辑分离到可测试的模型中(不强制架构)
- 操作处理程序应引用方法,而非包含内联逻辑
- 使用相对布局而非硬编码常量
- 视图应在任何上下文中工作(不假设屏幕尺寸或呈现样式)
性能
- - 仅传递所需值给视图(避免大型配置或上下文对象)
- 消除不必要的依赖以减少更新扩散
- 在热路径中分配状态前检查值是否变化
- 避免在onReceive、onChange、滚动处理程序中进行冗余状态更新
- 最小化频繁执行代码路径中的工作量
- 对大型列表使用LazyVStack/LazyHStack
- 为ForEach使用稳定标识(动态内容绝不使用.indices)
- 确保每个ForEach元素的视图数量恒定
- 避免在ForEach中进行内联过滤(预过滤并缓存)
- 避免在列表行中使用AnyView
- 考虑使用POD视图实现快速差异比较(或将昂贵视图包装在POD父视图中)
- 遇到UIImage(data:)时建议图像降采样(作为可选优化)
- 避免布局抖动(深层层次结构、过多的GeometryReader)
- 通过阈值限制频繁的几何更新
- 使用Self._printChanges()调试意外的视图更新
动画
- - 使用带value参数的.animation(_:value:)(不带value的已弃用版本过于宽泛)
- 对事件驱动的动画使用withAnimation(按钮点击、手势)
- 优先使用变换(offset、scale、rotation)而非布局变化(frame)以提高性能
- 过渡需要在条件结构外部有动画
- 自定义Animatable实现必须具有显式的animatableData
- 对多步序列使用.phaseAnimator(iOS 17+)
- 对精确时间控制使用.keyframeAnimator(iOS 17+)
- 动画完成处理程序需要.transaction(value:)以重新执行
- 隐式动画覆盖显式动画(视图树中后声明的生效)
Liquid Glass(iOS 26+)
仅在用户明确要求时采用。
- - 使用原生glassEffect、GlassEffectContainer和玻璃按钮样式
- 将多个玻璃元素包裹在GlassEffectContainer中
- 在布局和视觉修饰符之后应用.glassEffect()
- 仅对可点击/可聚焦元素使用.interactive()
- 使用带@Namespace的glassEffectID实现变形过渡
快速参考
属性包装器选择(现代)
| 包装器 | 使用场景 |
|---|
| @State | 内部视图状态(必须为private),或自有的@Observable类 |
| @Binding |
子视图修改父视图状态 |
| @Bindable | 需要绑定的注入@Observable |
| let | 来自父视图的只读值 |
| var | 通过.on