☰
swiftui-expert-skill - animation-advanced
2026/10/12 3:42:30 网站建设 项目流程

SwiftUI 进阶动画

事务、阶段动画(iOS 17+)、关键帧动画(iOS 17+)、完成处理程序(iOS 17+)以及@Animatable宏(iOS 26+)。

目录

  • 事务
  • 阶段动画(iOS 17+)
  • 关键帧动画(iOS 17+)
  • 动画完成处理程序(iOS 17+)
  • @Animatable 宏(iOS 26+)

事务

SwiftUI 中所有动画的底层机制。

基本用法

// withAnimation 是 withTransaction 的简写withAnimation(.default){flag.toggle()}// 等价的显式事务vartransaction=Transaction(animation:.default)withTransaction(transaction){flag.toggle()}

.transaction 修饰符

Rectangle().frame(width:flag?100:50,height:50).transaction{tint.animation=.default}

注意:它的行为类似于已弃用的.animation(_:)(不带 value 参数)——它会在每次状态变化时动画。

动画优先级

隐式动画覆盖显式动画(视图树中更靠后的胜出)。

Button("Tap"){withAnimation(.linear){flag.toggle()}}.animation(.bouncy,value:flag)// .bouncy 胜出!

禁用动画

// 阻止隐式动画覆盖.transaction{tint.disablesAnimations=true}// 完全移除动画.transaction{$0.animation=nil}

自定义事务键(iOS 17+)

通过事务传递元数据。

structChangeSourceKey:TransactionKey{staticletdefaultValue:String="unknown"}extensionTransaction{varchangeSource:String{get{self[ChangeSourceKey.self]}set{self[ChangeSourceKey.self]=newValue}}}// 设置来源vartransaction=Transaction(animation:.default)transaction.changeSource="server"withTransaction(transaction){flag.toggle()}// 在视图树中读取.transaction{tinift.changeSource=="server"{t.animation=.smooth}else{t.animation=.bouncy}}

阶段动画(iOS 17+)

自动循环切换离散阶段。每个阶段变化都是一次独立的动画。

基本用法

// 好——触发的阶段动画Button("Shake"){trigger+=1}.phaseAnimator([0.0,-10.0,10.0,-5.0,5.0,0.0],trigger:trigger){content,offsetincontent.offset(x:offset)}// 无限循环(无触发)Circle().phaseAnimator([1.0,1.2,1.0]){content,scaleincontent.scaleEffect(scale)}

枚举阶段(为清晰起见推荐)

// 好——枚举阶段自文档化enumBouncePhase:CaseIterable{caseinitial,up,down,settlevarscale:CGFloat{switchself{case.initial:1.0case.up:1.2case.down:0.9case.settle:1.0}}}Circle().phaseAnimator(BouncePhase.allCases,trigger:trigger){content,phaseincontent.scaleEffect(phase.scale)}

每个阶段的自定义时序

.phaseAnimator([0,-20,20],trigger:trigger){content,offsetincontent.offset(x:offset)}animation:{phaseinswitchphase{case-20:.bouncycase20:.lineardefault:.smooth}}

好与坏

// 好——多步序列使用 phaseAnimator.phaseAnimator([0,-10,10,0],trigger:trigger){content,offsetincontent.offset(x:offset)}// 坏——手动 DispatchQueue 排序Button("Animate"){withAnimation(.easeOut(duration:0.1)){offset=-10}DispatchQueue.main.asyncAfter(deadline:.now()+0.1){withAnimation{offset=10}}DispatchQueue.main.asyncAfter(deadline:.now()+0.2){withAnimation{offset=0}}}

关键帧动画(iOS 17+)

在特定时间点提供精确值的精细时序控制。

基本用法

Button("Bounce"){trigger+=1}.keyframeAnimator(initialValue:AnimationValues(),trigger:trigger){content,valueincontent.scaleEffect(value.scale).offset(y:value.verticalOffset)}keyframes:{_inKeyframeTrack(\.scale){SpringKeyframe(1.2,duration:0.15)SpringKeyframe(0.9,duration:0.1)SpringKeyframe(1.0,duration:0.15)}KeyframeTrack(\.verticalOffset){LinearKeyframe(-20,duration:0.15)LinearKeyframe(0,duration:0.25)}}structAnimationValues{varscale:CGFloat=1.0varverticalOffset:CGFloat=0}

关键帧类型

类型行为
CubicKeyframe平滑插值
LinearKeyframe直线插值
SpringKeyframe弹簧物理
MoveKeyframe瞬间跳跃(无插值)

多个同步轨道

轨道并行运行,每个轨道动画化一个属性。

// 好——带同步旋转和缩放的铃铛晃动structBellAnimation{varrotation:Double=0varscale:CGFloat=1.0}Image(systemName:"bell.fill").keyframeAnimator(initialValue:BellAnimation(),trigger:trigger){content,valueincontent.rotationEffect(.degrees(value.rotation)).scaleEffect(value.scale)}keyframes:{_inKeyframeTrack(\.rotation){CubicKeyframe(15,duration:0.1)CubicKeyframe(-15,duration:0.1)CubicKeyframe(10,duration:0.1)CubicKeyframe(-10,duration:0.1)CubicKeyframe(0,duration:0.1)}KeyframeTrack(\.scale){CubicKeyframe(1.1,duration:0.25)CubicKeyframe(1.0,duration:0.25)}}// 坏——手动基于定时器的动画Image(systemName:"bell.fill").onTapGesture{withAnimation(.easeOut(duration:0.1)){rotation=15}DispatchQueue.main.asyncAfter(deadline:.now()+0.1){withAnimation{rotation=-15}}// ... 更多手动时序——容易出错}

KeyframeTimeline(iOS 17+)

直接查询动画值,用于测试或非 SwiftUI 场景。

lettimeline=KeyframeTimeline(initialValue:AnimationValues()){KeyframeTrack(\.scale){CubicKeyframe(1.2,duration:0.25)CubicKeyframe(1.0,duration:0.25)}}letmidpoint=timeline.value(time:0.25)print(midpoint.scale)// 0.25 秒时的值

动画完成处理程序(iOS 17+)

动画完成时执行代码。

配合 withAnimation

// 好——withAnimation 的完成处理Button("Animate"){withAnimation(.spring){isExpanded.toggle()}completion:{showNextStep=true}}

配合事务(用于重新执行)

// 好——每次触发变化时完成处理都会触发Circle().scaleEffect(bounceCount%2==0?1.0:1.2).transaction(value:bounceCount){transactionintransaction.animation=.spring transaction.addAnimationCompletion{message="Bounce\(bounceCount)complete"}}// 坏——完成处理只触发一次(没有 value 参数)Circle().scaleEffect(bounceCount%2==0?1.0:1.2).animation(.spring,value:bounceCount).transaction{transactionin// 没有 value!transaction.addAnimationCompletion{completionCount+=1// 永远只触发一次}}

@Animatable 宏(iOS 26+)

@Animatable宏从所有可动画的存储属性自动合成animatableData,消除了冗长的手动遵循代码。使用@AnimatableIgnored排除不应动画化的属性。

之前(手动)

structWedge:Shape{varstartAngle:AnglevarendAngle:AnglevardrawClockwise:BoolvaranimatableData:AnimatablePair<Double,Double>{get{AnimatablePair(startAngle.radians,endAngle.radians)}set{startAngle=.radians(newValue.first)endAngle=.radians(newValue.second)}}funcpath(inrect:CGRect)->Path{/* ... */}}

之后(@Animatable)

@AnimatablestructWedge:Shape{varstartAngle:AnglevarendAngle:Angle@AnimatableIgnoredvardrawClockwise:Boolfuncpath(inrect:CGRect)->Path{/* ... */}}

何时使用

  • 优先使用@Animatable:适用于任何自定义Shape、AnimatableModifier或具有多个属性且遵循Animatable的类型
  • 使用@AnimatableIgnored:用于控制行为但不应插值的属性(例如方向、标志、标识符)
  • 宏适用于任何遵循Animatable的类型,不只是Shape

来源:“What’s new in SwiftUI”(WWDC25,会话 256)

何时手动实现animatableData

当插值值需要无法与存储属性一一对应的自定义逻辑时——归一化、钳制或驱动派生值——请使用显式的animatableData(而不是宏)。部署目标为 iOS 26+ 时使用AnimatableValues;更早目标使用AnimatablePair。

// iOS 26+:在插值期间将 phase 保持在 0..<2π 并钳制 amplitudestructWaveShape:Shape{varamplitude:CGFloatvarphase:CGFloatvarmaxAmplitude:CGFloatvaranimatableData:AnimatableValues<CGFloat,CGFloat>{get{AnimatableValues(amplitude,phase)}set{amplitude=min(max(newValue.value.0,0),maxAmplitude)phase=newValue.value.1.truncatingRemainder(dividingBy:2*.pi)}}funcpath(inrect:CGRect)->Path{/* ... */}}

在更早的部署目标上,同样的逻辑使用AnimatablePair配合newValue.first/newValue.second。


快速参考

事务(所有 iOS 版本)

  • withTransaction是withAnimation的显式形式
  • 隐式动画覆盖显式动画(视图树中更靠后的胜出)
  • 使用disablesAnimations防止覆盖
  • 使用.transaction { $0.animation = nil }移除动画

自定义事务键(iOS 17+)

  • 通过TransactionKey经动画系统传递元数据

阶段动画(iOS 17+)

  • 用于返回起点的多步序列
  • 为清晰起见优先使用枚举阶段
  • 每个阶段变化都是一次独立的动画
  • 一次性动画使用trigger参数

关键帧动画(iOS 17+)

  • 用于精确的时序控制
  • 轨道并行运行
  • 测试/高级用途使用KeyframeTimeline
  • 优先于手动 DispatchQueue 时序

完成处理程序(iOS 17+)

  • 一次性完成处理程序使用withAnimation(.animation) { } completion: { }
  • 需要在每次值变化时重新触发的处理程序使用.transaction(value:)
  • 没有value:参数时,完成处理只触发一次

@Animatable 宏(iOS 26+)

  • 使用@Animatable从存储属性自动合成animatableData
  • 使用@AnimatableIgnored排除不可动画的属性
  • 取代冗长的手动animatableDatagetter/setter

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询