swiftui-animation
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSwiftUI Animation (iOS 26+)
SwiftUI 动画(iOS 26+)
Review, write, and fix SwiftUI animations. Apply modern animation APIs with
correct timing, transitions, and accessibility handling using Swift 6.2 patterns.
审查、编写和修复SwiftUI动画。使用Swift 6.2的模式,应用具有正确时序、转场效果和无障碍处理的现代动画API。
Triage Workflow
问题排查流程
Step 1: Identify the animation category
步骤1:识别动画类别
| Category | API | When to use |
|---|---|---|
| State-driven | | Simple property changes |
| Multi-phase | | Sequenced multi-step animations |
| Keyframe | | Complex multi-property choreography |
| Shared element | | Layout-driven hero transitions |
| Navigation | | NavigationStack push/pop zoom |
| View lifecycle | | Insertion and removal |
| Text content | | In-place text/number changes |
| Symbol | | SF Symbol animations |
| Custom | | Novel timing curves |
| 类别 | API | 使用场景 |
|---|---|---|
| 状态驱动 | | 简单属性变更 |
| 多阶段 | | 序列式多步骤动画 |
| 关键帧 | | 复杂多属性协同动画 |
| 共享元素 | | 布局驱动的共享元素转场 |
| 导航 | | NavigationStack 推入/弹出缩放效果 |
| 视图生命周期 | | 视图插入与移除 |
| 文本内容 | | 文本/数字原地变更 |
| 图标 | | SF Symbol 动画 |
| 自定义 | | 自定义时序曲线 |
Step 2: Choose the animation curve
步骤2:选择动画曲线
swift
// Timing curves
.linear // constant speed
.easeIn(duration: 0.3) // slow start
.easeOut(duration: 0.3) // slow end
.easeInOut(duration: 0.3) // slow start and end
// Spring presets (preferred for natural motion)
.smooth // no bounce, fluid
.smooth(duration: 0.5, extraBounce: 0.0)
.snappy // small bounce, responsive
.snappy(duration: 0.4, extraBounce: 0.1)
.bouncy // visible bounce, playful
.bouncy(duration: 0.5, extraBounce: 0.2)
// Custom spring
.spring(duration: 0.5, bounce: 0.3, blendDuration: 0.0)
.spring(Spring(duration: 0.6, bounce: 0.2), blendDuration: 0.0)
.interactiveSpring(response: 0.15, dampingFraction: 0.86)swift
// 时序曲线
.linear // 匀速
.easeIn(duration: 0.3) // 慢启动
.easeOut(duration: 0.3) // 慢结束
.easeInOut(duration: 0.3) // 慢启动+慢结束
// 弹簧预设(自然动效首选)
.smooth // 无弹跳,流畅
.smooth(duration: 0.5, extraBounce: 0.0)
.snappy // 小幅弹跳,响应迅速
.snappy(duration: 0.4, extraBounce: 0.1)
.bouncy // 明显弹跳,活泼
.bouncy(duration: 0.5, extraBounce: 0.2)
// 自定义弹簧
.spring(duration: 0.5, bounce: 0.3, blendDuration: 0.0)
.spring(Spring(duration: 0.6, bounce: 0.2), blendDuration: 0.0)
.interactiveSpring(response: 0.15, dampingFraction: 0.86)Step 3: Apply and verify
步骤3:应用与验证
- Confirm animation triggers on the correct state change.
- Test with Accessibility > Reduce Motion enabled.
- Verify no expensive work runs inside animation content closures.
- 确认动画触发正确的状态变更。
- 测试开启“辅助功能 > 减少动态效果”后的表现。
- 确保动画内容闭包内没有执行高开销操作。
withAnimation (Explicit Animation)
withAnimation(显式动画)
swift
withAnimation(.spring) { isExpanded.toggle() }
// With completion (iOS 17+)
withAnimation(.smooth(duration: 0.35), completionCriteria: .logicallyComplete) {
isExpanded = true
} completion: { loadContent() }swift
withAnimation(.spring) { isExpanded.toggle() }
// 带完成回调(iOS 17+)
withAnimation(.smooth(duration: 0.35), completionCriteria: .logicallyComplete) {
isExpanded = true
} completion: { loadContent() }.animation(_:value:) (Implicit Animation)
.animation(_:value:)(隐式动画)
swift
Circle()
.scaleEffect(isActive ? 1.2 : 1.0)
.opacity(isActive ? 1.0 : 0.6)
.animation(.bouncy, value: isActive)swift
Circle()
.scaleEffect(isActive ? 1.2 : 1.0)
.opacity(isActive ? 1.0 : 0.6)
.animation(.bouncy, value: isActive)Spring Type (iOS 17+)
Spring 类型(iOS 17+)
Four initializer forms for different mental models.
swift
// Perceptual (preferred)
Spring(duration: 0.5, bounce: 0.3)
// Physical
Spring(mass: 1.0, stiffness: 100.0, damping: 10.0)
// Response-based
Spring(response: 0.5, dampingRatio: 0.7)
// Settling-based
Spring(settlingDuration: 1.0, dampingRatio: 0.8)Three presets mirror Animation presets: , , .
.smooth.snappy.bouncy四种初始化方式,适配不同的思维模型。
swift
// 感知优先(推荐)
Spring(duration: 0.5, bounce: 0.3)
// 物理参数
Spring(mass: 1.0, stiffness: 100.0, damping: 10.0)
// 响应时间
Spring(response: 0.5, dampingRatio: 0.7)
// 稳定时间
Spring(settlingDuration: 1.0, dampingRatio: 0.8)三种预设与Animation预设对应:, , 。
.smooth.snappy.bouncyPhaseAnimator (iOS 17+)
PhaseAnimator(iOS 17+)
Cycle through discrete phases with per-phase animation curves.
swift
enum PulsePhase: CaseIterable {
case idle, grow, shrink
}
struct PulsingDot: View {
var body: some View {
PhaseAnimator(PulsePhase.allCases) { phase in
Circle()
.frame(width: 40, height: 40)
.scaleEffect(phase == .grow ? 1.4 : 1.0)
.opacity(phase == .shrink ? 0.5 : 1.0)
} animation: { phase in
switch phase {
case .idle: .easeIn(duration: 0.2)
case .grow: .spring(duration: 0.4, bounce: 0.3)
case .shrink: .easeOut(duration: 0.3)
}
}
}
}Trigger-based variant runs one cycle per trigger change:
swift
PhaseAnimator(PulsePhase.allCases, trigger: tapCount) { phase in
// ...
} animation: { _ in .spring(duration: 0.4) }循环切换离散阶段,每个阶段可配置独立的动画曲线。
swift
enum PulsePhase: CaseIterable {
case idle, grow, shrink
}
struct PulsingDot: View {
var body: some View {
PhaseAnimator(PulsePhase.allCases) { phase in
Circle()
.frame(width: 40, height: 40)
.scaleEffect(phase == .grow ? 1.4 : 1.0)
.opacity(phase == .shrink ? 0.5 : 1.0)
} animation: { phase in
switch phase {
case .idle: .easeIn(duration: 0.2)
case .grow: .spring(duration: 0.4, bounce: 0.3)
case .shrink: .easeOut(duration: 0.3)
}
}
}
}触发式变体:每次触发器变更时运行一轮循环:
swift
PhaseAnimator(PulsePhase.allCases, trigger: tapCount) { phase in
// ...
} animation: { _ in .spring(duration: 0.4) }KeyframeAnimator (iOS 17+)
KeyframeAnimator(iOS 17+)
Animate multiple properties along independent timelines.
swift
struct AnimValues {
var scale: Double = 1.0
var yOffset: Double = 0.0
var opacity: Double = 1.0
}
struct BounceView: View {
@State private var trigger = false
var body: some View {
Image(systemName: "star.fill")
.font(.largeTitle)
.keyframeAnimator(
initialValue: AnimValues(),
trigger: trigger
) { content, value in
content
.scaleEffect(value.scale)
.offset(y: value.yOffset)
.opacity(value.opacity)
} keyframes: { _ in
KeyframeTrack(\.scale) {
SpringKeyframe(1.5, duration: 0.3)
CubicKeyframe(1.0, duration: 0.4)
}
KeyframeTrack(\.yOffset) {
CubicKeyframe(-30, duration: 0.2)
CubicKeyframe(0, duration: 0.4)
}
KeyframeTrack(\.opacity) {
LinearKeyframe(0.6, duration: 0.15)
LinearKeyframe(1.0, duration: 0.25)
}
}
.onTapGesture { trigger.toggle() }
}
}Keyframe types: (linear), (smooth curve),
(spring physics), (instant jump).
LinearKeyframeCubicKeyframeSpringKeyframeMoveKeyframeUse for looping keyframe animations.
repeating: true沿独立时间线为多个属性设置动画。
swift
struct AnimValues {
var scale: Double = 1.0
var yOffset: Double = 0.0
var opacity: Double = 1.0
}
struct BounceView: View {
@State private var trigger = false
var body: some View {
Image(systemName: "star.fill")
.font(.largeTitle)
.keyframeAnimator(
initialValue: AnimValues(),
trigger: trigger
) { content, value in
content
.scaleEffect(value.scale)
.offset(y: value.yOffset)
.opacity(value.opacity)
} keyframes: { _ in
KeyframeTrack(\.scale) {
SpringKeyframe(1.5, duration: 0.3)
CubicKeyframe(1.0, duration: 0.4)
}
KeyframeTrack(\.yOffset) {
CubicKeyframe(-30, duration: 0.2)
CubicKeyframe(0, duration: 0.4)
}
KeyframeTrack(\.opacity) {
LinearKeyframe(0.6, duration: 0.15)
LinearKeyframe(1.0, duration: 0.25)
}
}
.onTapGesture { trigger.toggle() }
}
}关键帧类型:(线性)、(平滑曲线)、(弹簧物理)、(瞬间跳转)。
LinearKeyframeCubicKeyframeSpringKeyframeMoveKeyframe设置可实现循环关键帧动画。
repeating: true@Animatable Macro
@Animatable 宏
Replaces manual boilerplate. Attach to any type with
animatable stored properties.
AnimatableDataswift
// WRONG: Manual AnimatableData (verbose, error-prone)
struct WaveShape: Shape, Animatable {
var frequency: Double
var amplitude: Double
var phase: Double
var animatableData: AnimatablePair<Double, AnimatablePair<Double, Double>> {
get { AnimatablePair(frequency, AnimatablePair(amplitude, phase)) }
set {
frequency = newValue.first
amplitude = newValue.second.first
phase = newValue.second.second
}
}
// ...
}
// CORRECT: @Animatable macro synthesizes animatableData
@Animatable
struct WaveShape: Shape {
var frequency: Double
var amplitude: Double
var phase: Double
@AnimatableIgnored var lineWidth: CGFloat
func path(in rect: CGRect) -> Path {
// draw wave using frequency, amplitude, phase
}
}Rules:
- Stored properties must conform to .
VectorArithmetic - Use to exclude non-animatable properties.
@AnimatableIgnored - Computed properties are never included.
替代手动编写的冗余代码。可附加到任何包含可动画存储属性的类型。
AnimatableDataswift
// 错误写法:手动编写AnimatableData(冗长且易出错)
struct WaveShape: Shape, Animatable {
var frequency: Double
var amplitude: Double
var phase: Double
var animatableData: AnimatablePair<Double, AnimatablePair<Double, Double>> {
get { AnimatablePair(frequency, AnimatablePair(amplitude, phase)) }
set {
frequency = newValue.first
amplitude = newValue.second.first
phase = newValue.second.second
}
}
// ...
}
// 正确写法:@Animatable宏自动生成animatableData
@Animatable
struct WaveShape: Shape {
var frequency: Double
var amplitude: Double
var phase: Double
@AnimatableIgnored var lineWidth: CGFloat
func path(in rect: CGRect) -> Path {
// 使用frequency、amplitude、phase绘制波形
}
}规则:
- 存储属性必须遵循协议。
VectorArithmetic - 使用排除不可动画的属性。
@AnimatableIgnored - 计算属性永远不会被包含。
matchedGeometryEffect (iOS 14+)
matchedGeometryEffect(iOS 14+)
Synchronize geometry between views for shared-element animations.
swift
struct HeroView: View {
@Namespace private var heroSpace
@State private var isExpanded = false
var body: some View {
if isExpanded {
DetailCard()
.matchedGeometryEffect(id: "card", in: heroSpace)
.onTapGesture {
withAnimation(.spring(duration: 0.4, bounce: 0.2)) {
isExpanded = false
}
}
} else {
ThumbnailCard()
.matchedGeometryEffect(id: "card", in: heroSpace)
.onTapGesture {
withAnimation(.spring(duration: 0.4, bounce: 0.2)) {
isExpanded = true
}
}
}
}
}Exactly one view per ID must be visible at a time for the interpolation to work.
同步不同视图的几何信息,实现共享元素动画。
swift
struct HeroView: View {
@Namespace private var heroSpace
@State private var isExpanded = false
var body: some View {
if isExpanded {
DetailCard()
.matchedGeometryEffect(id: "card", in: heroSpace)
.onTapGesture {
withAnimation(.spring(duration: 0.4, bounce: 0.2)) {
isExpanded = false
}
}
} else {
ThumbnailCard()
.matchedGeometryEffect(id: "card", in: heroSpace)
.onTapGesture {
withAnimation(.spring(duration: 0.4, bounce: 0.2)) {
isExpanded = true
}
}
}
}
}同一时间每个ID只能对应一个可见视图,才能保证插值效果正常工作。
Navigation Zoom Transition (iOS 18+)
导航缩放转场(iOS 18+)
Pair on the source view with
on the destination.
matchedTransitionSource.navigationTransition(.zoom(...))swift
struct GalleryView: View {
@Namespace private var zoomSpace
let items: [GalleryItem]
var body: some View {
NavigationStack {
ScrollView {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))]) {
ForEach(items) { item in
NavigationLink {
GalleryDetail(item: item)
.navigationTransition(
.zoom(sourceID: item.id, in: zoomSpace)
)
} label: {
ItemThumbnail(item: item)
.matchedTransitionSource(
id: item.id, in: zoomSpace
)
}
}
}
}
}
}
}Apply on the destination view, not on inner containers.
.navigationTransition在源视图上使用,在目标视图上搭配。
matchedTransitionSource.navigationTransition(.zoom(...))swift
struct GalleryView: View {
@Namespace private var zoomSpace
let items: [GalleryItem]
var body: some View {
NavigationStack {
ScrollView {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))]) {
ForEach(items) { item in
NavigationLink {
GalleryDetail(item: item)
.navigationTransition(
.zoom(sourceID: item.id, in: zoomSpace)
)
} label: {
ItemThumbnail(item: item)
.matchedTransitionSource(
id: item.id, in: zoomSpace
)
}
}
}
}
}
}
}.navigationTransitionTransitions (iOS 17+)
转场效果(iOS 17+)
Control how views animate on insertion and removal.
swift
if showBanner {
BannerView()
.transition(.move(edge: .top).combined(with: .opacity))
}Built-in types: , , , ,
, , , ,
, , ,
.
.opacity.slide.scale.scale(_:anchor:).move(edge:).push(from:).offset(x:y:).identity.blurReplace.blurReplace(_:).symbolEffect.symbolEffect(_:options:)Asymmetric transitions:
swift
.transition(.asymmetric(
insertion: .push(from: .bottom),
removal: .opacity
))控制视图插入和移除时的动画效果。
swift
if showBanner {
BannerView()
.transition(.move(edge: .top).combined(with: .opacity))
}内置类型:, , , , , , , , , , , 。
.opacity.slide.scale.scale(_:anchor:).move(edge:).push(from:).offset(x:y:).identity.blurReplace.blurReplace(_:).symbolEffect.symbolEffect(_:options:)不对称转场:
swift
.transition(.asymmetric(
insertion: .push(from: .bottom),
removal: .opacity
))ContentTransition (iOS 16+)
ContentTransition(iOS 16+)
Animate in-place content changes without insertion/removal.
swift
Text("\(score)")
.contentTransition(.numericText(countsDown: false))
.animation(.snappy, value: score)
// For SF Symbols
Image(systemName: isMuted ? "speaker.slash" : "speaker.wave.3")
.contentTransition(.symbolEffect(.replace.downUp))Types: , , ,
, , .
.identity.interpolate.opacity.numericText(countsDown:).numericText(value:).symbolEffect无需插入/移除视图,即可为原地内容变更设置动画。
swift
Text("\(score)")
.contentTransition(.numericText(countsDown: false))
.animation(.snappy, value: score)
// SF Symbol 示例
Image(systemName: isMuted ? "speaker.slash" : "speaker.wave.3")
.contentTransition(.symbolEffect(.replace.downUp))类型:, , , , , 。
.identity.interpolate.opacity.numericText(countsDown:).numericText(value:).symbolEffectSymbol Effects (iOS 17+)
Symbol 效果(iOS 17+)
Animate SF Symbols with semantic effects.
swift
// Discrete (triggers on value change)
Image(systemName: "bell.fill")
.symbolEffect(.bounce, value: notificationCount)
Image(systemName: "arrow.clockwise")
.symbolEffect(.wiggle.clockwise, value: refreshCount)
// Indefinite (active while condition holds)
Image(systemName: "wifi")
.symbolEffect(.pulse, isActive: isSearching)
Image(systemName: "mic.fill")
.symbolEffect(.breathe, isActive: isRecording)
// Variable color with chaining
Image(systemName: "speaker.wave.3.fill")
.symbolEffect(
.variableColor.iterative.reversing.dimInactiveLayers,
options: .repeating,
isActive: isPlaying
)All effects: , , , , ,
, , , , .
.bounce.pulse.variableColor.scale.appear.disappear.replace.breathe.rotate.wiggleScope: , . Direction varies per effect.
.byLayer.wholeSymbol为SF Symbol添加语义化动画。
swift
// 离散触发(值变更时触发)
Image(systemName: "bell.fill")
.symbolEffect(.bounce, value: notificationCount)
Image(systemName: "arrow.clockwise")
.symbolEffect(.wiggle.clockwise, value: refreshCount)
// 持续动画(条件满足时激活)
Image(systemName: "wifi")
.symbolEffect(.pulse, isActive: isSearching)
Image(systemName: "mic.fill")
.symbolEffect(.breathe, isActive: isRecording)
// 可变颜色与链式调用
Image(systemName: "speaker.wave.3.fill")
.symbolEffect(
.variableColor.iterative.reversing.dimInactiveLayers,
options: .repeating,
isActive: isPlaying
)所有效果:, , , , , , , , , 。
.bounce.pulse.variableColor.scale.appear.disappear.replace.breathe.rotate.wiggle作用范围:, 。方向因效果而异。
.byLayer.wholeSymbolCommon Mistakes
常见错误
1. Animating without a value binding
1. 动画未绑定值
swift
// WRONG: .animation without value triggers on any state change
Text("Hello")
.opacity(isVisible ? 1 : 0)
.animation(.easeIn)
// CORRECT: Bind to the specific value
Text("Hello")
.opacity(isVisible ? 1 : 0)
.animation(.easeIn, value: isVisible)swift
// 错误:无value的.animation会在任意状态变更时触发
Text("Hello")
.opacity(isVisible ? 1 : 0)
.animation(.easeIn)
// 正确:绑定到特定值
Text("Hello")
.opacity(isVisible ? 1 : 0)
.animation(.easeIn, value: isVisible)2. Expensive work inside animation closures
2. 动画闭包内执行高开销操作
swift
// WRONG: Heavy computation every frame
.keyframeAnimator(initialValue: vals, trigger: t) { content, value in
let filtered = applyExpensiveFilter(content) // runs every frame
return filtered.opacity(value.opacity)
} keyframes: { _ in /* ... */ }
// CORRECT: Precompute outside, animate only visual properties
.keyframeAnimator(initialValue: vals, trigger: t) { content, value in
content.opacity(value.opacity)
} keyframes: { _ in /* ... */ }swift
// 错误:每帧都执行重计算
.keyframeAnimator(initialValue: vals, trigger: t) { content, value in
let filtered = applyExpensiveFilter(content) // 每帧运行
return filtered.opacity(value.opacity)
} keyframes: { _ in /* ... */ }
// 正确:提前计算,仅动画视觉属性
.keyframeAnimator(initialValue: vals, trigger: t) { content, value in
content.opacity(value.opacity)
} keyframes: { _ in /* ... */ }3. Missing reduce motion support
3. 未支持减少动态效果
swift
// WRONG: Ignores accessibility setting
withAnimation(.bouncy) { showDetail = true }
// CORRECT: Respect reduce motion
@Environment(\.accessibilityReduceMotion) private var reduceMotion
withAnimation(reduceMotion ? .none : .bouncy) { showDetail = true }swift
// 错误:忽略辅助功能设置
withAnimation(.bouncy) { showDetail = true }
// 正确:遵循减少动态效果设置
@Environment(\.accessibilityReduceMotion) private var reduceMotion
withAnimation(reduceMotion ? .none : .bouncy) { showDetail = true }4. Multiple matchedGeometryEffect sources
4. 多个matchedGeometryEffect源视图
swift
// WRONG: Both visible with same ID -- undefined behavior
HStack {
Circle().matchedGeometryEffect(id: "dot", in: ns)
Circle().matchedGeometryEffect(id: "dot", in: ns)
}
// CORRECT: Only one source visible at a time via conditional
if onLeft {
Circle().matchedGeometryEffect(id: "dot", in: ns)
} else {
Circle().matchedGeometryEffect(id: "dot", in: ns)
}swift
// 错误:同一ID对应两个可见视图——行为未定义
HStack {
Circle().matchedGeometryEffect(id: "dot", in: ns)
Circle().matchedGeometryEffect(id: "dot", in: ns)
}
// 正确:通过条件控制同一时间仅一个源视图可见
if onLeft {
Circle().matchedGeometryEffect(id: "dot", in: ns)
} else {
Circle().matchedGeometryEffect(id: "dot", in: ns)
}5. Using DispatchQueue or UIView.animate
5. 使用DispatchQueue或UIView.animate
swift
// WRONG: UIKit patterns in SwiftUI
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
withAnimation { isVisible = true }
}
UIView.animate(withDuration: 0.3) { /* ... */ }
// CORRECT: SwiftUI animation with delay
withAnimation(.spring.delay(0.5)) { isVisible = true }
withAnimation(.easeInOut(duration: 0.3)) { /* state change */ }swift
// 错误:在SwiftUI中使用UIKit模式
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
withAnimation { isVisible = true }
}
UIView.animate(withDuration: 0.3) { /* ... */ }
// 正确:使用SwiftUI动画的延迟参数
withAnimation(.spring.delay(0.5)) { isVisible = true }
withAnimation(.easeInOut(duration: 0.3)) { /* 状态变更 */ }6. Forgetting animation on ContentTransition
6. ContentTransition未搭配动画
swift
// WRONG: No animation -- content transition has no effect
Text("\(count)")
.contentTransition(.numericText(countsDown: true))
// CORRECT: Pair with animation modifier
Text("\(count)")
.contentTransition(.numericText(countsDown: true))
.animation(.snappy, value: count)swift
// 错误:无动画——内容转场无效
Text("\(count)")
.contentTransition(.numericText(countsDown: true))
// 正确:与animation修饰符搭配使用
Text("\(count)")
.contentTransition(.numericText(countsDown: true))
.animation(.snappy, value: count)7. navigationTransition on wrong view
7. navigationTransition应用在错误视图
swift
// WRONG: Applied inside a container
NavigationLink {
VStack {
DetailView(item: item)
.navigationTransition(.zoom(sourceID: item.id, in: ns))
}
} label: { /* ... */ }
// CORRECT: Applied on the outermost destination view
NavigationLink {
DetailView(item: item)
.navigationTransition(.zoom(sourceID: item.id, in: ns))
} label: { /* ... */ }swift
// 错误:应用在容器内部
NavigationLink {
VStack {
DetailView(item: item)
.navigationTransition(.zoom(sourceID: item.id, in: ns))
}
} label: { /* ... */ }
// 正确:应用在最外层的目标视图
NavigationLink {
DetailView(item: item)
.navigationTransition(.zoom(sourceID: item.id, in: ns))
} label: { /* ... */ }Review Checklist
审查清单
- Animation curve matches intent (spring for natural, ease for mechanical)
- wraps the state change, not the view
withAnimation - has an explicit
.animation(_:value:)parametervalue - has exactly one source per ID at a time
matchedGeometryEffect - Navigation zoom uses matching and
idon source and destinationnamespace - macro used instead of manual
@AnimatableanimatableData - is checked and respected
accessibilityReduceMotion - No heavy computation inside keyframe/phase content closures
- No or
DispatchQueuefor animation timingUIView.animate - Transitions use on conditionally inserted views
.transition() - is paired with
contentTransition.animation(_:value:) - Symbol effects use correct category (discrete vs indefinite)
- Ensure animated state changes happen on @MainActor; types driving animations should be Sendable if passed across concurrency boundaries
- 动画曲线符合预期(弹簧用于自然动效,缓动用于机械动效)
- 包裹的是状态变更,而非视图
withAnimation - 包含显式的
.animation(_:value:)参数value - 同一时间每个ID仅对应一个源视图
matchedGeometryEffect - 导航缩放转场在源视图和目标视图使用匹配的和
idnamespace - 使用宏替代手动编写
@AnimatableanimatableData - 检查并遵循设置
accessibilityReduceMotion - 关键帧/阶段内容闭包内无重计算操作
- 未使用或
DispatchQueue处理动画时序UIView.animate - 转场效果通过应用在条件插入的视图上
.transition() - 与
contentTransition搭配使用.animation(_:value:) - Symbol效果使用正确的类别(离散触发 vs 持续动画)
- 确保动画状态变更在@MainActor上执行;若跨并发边界传递,驱动动画的类型应遵循Sendable协议
Reference Material
参考资料
- See for CustomAnimation protocol, full Spring variants, all Transition types, symbol effect details, Transaction system, UnitCurve types, and performance guidance.
references/animation-advanced.md
- 查看了解CustomAnimation协议、完整的Spring变体、所有Transition类型、Symbol效果细节、Transaction系统、UnitCurve类型及性能优化指南。
references/animation-advanced.md