图像与设计
SwiftUI 视图需要状态变化、插入移除…
当 SwiftUI 视图应在状态变化、插入/移除、导航或多步编排中产生动效,或需要修复动画、过渡、SF Symbol 效果与 Reduce Motion/并发问题时使用。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 59/100 ×0.15
适用场景
SwiftUI 视图需要状态变化、插入移特定
解决思路
- 按动画类别选择 API:状态驱动用 `withAnimation`、`.animation(_:body:)`、`.animation(_:value:)`;多阶段用 `PhaseAnimator`;关键帧用 `KeyframeAnimator`;共享元素用 `matchedGeometryEffect`;导航用 `matchedTransitionSource` + `.navigationTransition(.zoom)`;视图生命周期插入/移除用 `.transition()`;文本内容用 `.contentTransition()`;SF Symbol 用 `.symbolEffect()`;自定义曲线用 `CustomAnimation`;桥接 Core Animation 用 `CALayer`、`CAAnimation`、`CADisplayLink`,并先读 `references/core-animation-bridge.md`。
- 选择动画曲线:机械感用 `.easeInOut(duration: 0.3)`;流畅无回弹用 `.smooth`;响应快小幅回弹用 `.snappy`;活泼明显回弹用 `.bouncy`;需要自定义弹簧用 `.spring(duration:bounce:)`;预设不足时查 `references/animation-advanced.md#spring-type-all-initializer-variants`。
- 应用后验证:确认动画在正确状态变化时触发;开启 Accessibility > Reduce Motion 测试;确认动画内容闭包内没有昂贵工作;CA 桥接时用 Coordinator 处理 delegate、使 display link 失效、把帧率范围当提示、按实际刷新率适配工作。
- 显式动画:用 `withAnimation(.spring) { isExpanded.toggle() }` 包裹状态变更;iOS 17+ 可用 `withAnimation(.smooth(duration: 0.35), completionCriteria: .logicallyComplete) { isExpanded = true } completion: { loadContent() }` 在动画后执行完成逻辑。
- 隐式动画:需要拥有状态变更时用 `withAnimation`;只动画选中 modifier 时用 `.animation(_:body:)`;简单值绑定变化用 `.animation(_:value:)`;示例:`Badge().foregroundStyle(...).animation(.snappy) { content in content.scaleEffect(...).opacity(...) }` 和 `Circle().scaleEffect(...).opacity(...).animation(.bouncy, value: isActive)`。
- Spring 类型:优先用感知形式或预设 `Spring(duration: 0.5, bounce: 0.3)`、`Spring.smooth`、`Spring.snappy`、`Spring.bouncy`;只有需要 physical、response-based 或 settling 参数时才加载 advanced reference。
- PhaseAnimator:用 `PhaseAnimator(PulsePhase.allCases) { phase in ... } animation: { phase in switch ... }` 按离散阶段循环,并为每个阶段设置不同曲线;用 trigger 变体 `PhaseAnimator(PulsePhase.allCases, trigger: tapCount) { phase in ... } animation: { _ in .spring(duration: 0.4) }` 在 trigger 变化时推进到下一阶段。
- KeyframeAnimator:用 `keyframeAnimator(initialValue: AnimValues(), trigger: trigger) { content, value in ... } keyframes: { _ in ... }` 沿独立时间线动画多个属性;用 `KeyframeTrack(\.scale)`、`KeyframeTrack(\.yOffset)`、`KeyframeTrack(\.opacity)` 分别定义轨道;关键帧类型用 `LinearKeyframe`、`CubicKeyframe`、`SpringKeyframe`、`MoveKeyframe`;循环用 `repeating: true`;Swift 6 中 keyframe closure 是 `@Sendable`,要在 modifier 前捕获 state/env 值。
- @Animatable 宏:用 `@Animatable` 标注含可动画存储属性的类型(如 `Shape`),自动生成 `AnimatableData`,替换手写样板;存储属性必须符合 `VectorArithmetic`;用 `@AnimatableIgnored` 排除不可动画属性;计算属性不会自动包含。
- matchedGeometryEffect:用 `@Namespace private var heroSpace` 和 `.matchedGeometryEffect(id: "card", in: heroSpace)` 在源/目标视图间同步几何;在 `withAnimation(.spring(duration: 0.4, bounce: 0.2))` 中切换 `isExpanded`;同一 ID 同一时间只能有一个源视图可见,否则布局未定义。
- Navigation Zoom:在源视图用 `.matchedTransitionSource(id: item.id, in: zoomSpace)`,在目标视图用 `.navigationTransition(.zoom(sourceID: item.id, in: zoomSpace))`;`.navigationTransition` 必须放在目标视图最外层,不要放在内部容器上。
- Transitions:用 `.transition(.move(edge: .top).combined(with: .opacity))` 控制插入/移除;不对称过渡用 `.transition(.asymmetric(insertion: .push(from: .bottom), removal: .opacity))`;内置类型和自定义 `Transition` 见 `references/animation-advanced.md#all-transition-types-ios-17`。
- ContentTransition:用 `.contentTransition(.numericText(countsDown: false))` 配 `.animation(.snappy, value: score)` 动画原地数字变化;SF Symbol 替换用 `.contentTransition(.symbolEffect(.replace.downUp))`;可用类型:`.identity`、`.interpolate`、`.opacity`、`.numericText(countsDown:)`、`.numericText(value:)`、`.symbolEffect`。
- Symbol Effects:用 `.symbolEffect` 为 SF Symbol 添加语义效果;iOS 17+ 用 `.bounce`、`.pulse`、`.variableColor`、`.scale`、`.appear`、`.disappear`、`.replace`;iOS 18+ 用 `.breathe`、`.rotate`、`.wiggle`;离散触发用 `.symbolEffect(.bounce, value: notificationCount)` 或 `.symbolEffect(.wiggle.clockwise, value: refreshCount)`;持续激活用 `.symbolEffect(.pulse, isActive: isSearching)` 或 `.symbolEffect(.breathe, isActive: isRecording)`;链式 variable color 用 `.variableColor.iterative.reversing.dimInactiveLayers`,配 `options: .repeating` 和 `isActive: isPlaying`;scope 用 `.byLayer` 或 `.wholeSymbol`,direction 因效果而异。
- Symbol Rendering Modes:用 `.symbolRenderingMode(.monochrome/.hierarchical/.multicolor/.palette)` 选择渲染模式,用 `.foregroundStyle` 提供 palette 颜色;变量符号用 `Image(systemName:variableValue:)`(iOS 16+)按 0.0...1.0 填充,iOS 26+ 用 `.symbolVariableValueMode(.draw)` 或 `.color` 选择绘制/颜色。
- 避免裸 `.animation(_:)` 过宽:需要精确范围时改用 `.animation(.easeIn, value: isVisible)`、`.animation(.easeIn) { content in content.opacity(...) }`,或用 `withAnimation(.easeIn) { isVisible.toggle() }` 自己拥有状态变更。
- 避免在动画闭包内做昂贵工作或 actor-isolated 读取:`keyframeAnimator` / `PhaseAnimator` 内容闭包每帧运行;预计算昂贵值,只动画视觉属性,并在 `@Sendable` keyframe closure 前捕获 state/env 值。
- 支持 Reduce Motion:用 `@Environment(\.accessibilityReduceMotion) private var reduceMotion`;对 symbol 用 `.symbolEffectsRemoved(reduceMotion)` 移除继承效果;较大运动用 `reduceMotion ? .none : animation` 门控。
- 避免多个 matchedGeometryEffect 源:同一 ID 同一时间只能一个源视图可见,多个可见源会导致布局未定义。
- 不要用 `DispatchQueue` 或 `UIView.animate` 做延迟/动画:用 `withAnimation(.spring.delay(0.5)) { isVisible = true }` 替代 `DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { withAnimation { isVisible = true } }`。
- ContentTransition 必须配对动画:`Text("\(count)").contentTransition(.numericText(countsDown: true))` 要加 `.animation(.snappy, value: count)`,否则内容过渡无效果。
- navigationTransition 放对位置:把 `.navigationTransition(.zoom(sourceID:in:))` 放在最外层目标视图,不要放在容器内部。
- Review:检查动画曲线是否符合意图(自然动作用 spring,机械动作用 ease);`withAnimation` 是否包裹状态变更;隐式动画是否用 `.animation(_:body:)` 做选择性 modifier scope 或用 `.animation(_:value:)` 绑定显式 value。
- Review:`matchedGeometryEffect` 每个 ID 是否恰好一个源;zoom 是否使用匹配的 `id`/`namespace`。
- Review:能用 `@Animatable` 宏合成时是否使用;仅当自定义打包更清晰时保留手写 `animatableData`。
- Review:是否检查 `accessibilityReduceMotion`;是否没有 `DispatchQueue`/`UIView.animate`。
- Review:过渡是否用 `.transition()`;`contentTransition` 是否配对动画并使用最窄的合适隐式动画范围。
- Review:动画驱动的状态变更是否在 `@MainActor`;驱动动画的类型是否 `Sendable`。
- 参考:自定义 `CustomAnimation` 协议、Spring 变体、Transition 类型、symbol effects、Transaction 系统、UnitCurve、性能指导见 `references/animation-advanced.md`;Core Animation 桥接模式见 `references/core-animation-bridge.md`。
适用边界 · 注意事项
- 非 SwiftUI/Apple 平台,或目标 iOS 版本低于所用 API 最低版本时不适用;应改用兼容 API 或降级。
- 动画内容闭包需要执行昂贵计算、跨 actor 读取或阻塞主线程时不适用;应先预计算并只动画视觉属性。
- 用户启用 Reduce Motion 时不要强制播放大幅运动;应移除 symbol effects、将大动画门控为 `.none` 或提供替代。