当前位置: 首页 > 科技观察

AnimatableModifier

时间:2023-03-12 22:20:14 科技观察

高级SwiftUI动画的AnimatableModifier无法实现动画如果你第一次使用AnimatableModifier,你可能会遇到问题。写一个简单的动画,但是没有动画效果。我又试了几次都没有成功。所以我认为该功能不存在并且已被放弃。幸运的是,我后来坚持了下来。事实证明,我的第一个修改器非常好,但可动画修改器在容器中不起作用。在我第二次尝试时,动画视图不在容器内。例如下面的modifier可以动画成功:MyView().modifier(MyAnimatableModifier(value:flag?1:0))但是同样的代码,在VStack中没有动画:VStack{MyView().modifier(MyAnimatableModifier(value:flag?1:0))}在正式解决这个问题之前,经过尝试,可以在VStack中改成如下代码实现动画:VStack{Color.clear.overlay(MyView().modifier(MyAnimatableModifier(value:flag?1:0))).frame(width:100,height:100)}这个写的是用透明视图占用实际视图空间,动画放在透明视图上,使用.覆盖()。有点不方便的是,我们需要知道实际视图有多大,所以我们可以将透明视图的框架设置在它后面。实现代码可以在下面的示例中找到。动画文本首先你需要动画一些文本。对于这个例子,我们将创建一个进度加载指示器。很多人可能认为应该使用动画路径。但是,内部标签不能被动画化,这可以使用AnimatableModifier来实现。完整的代码在文章末尾的链接中,如示例10。关键代码如下:structPercentageIndicator:AnimatableModifier{varpct:CGFloat=0varanimatableData:CGFloat{get{pct}set{pct=newValue}}funcbody(content:Content)->someView{content.overlay(ArcShape(pct:pct).foregroundColor(.red)).overlay(LabelView(pct:pct))}structArcShape:Shape{letpct:CGFloatfuncpath(inrect:CGRect)->Path{varp=Path()p.addArc(center:CGPoint(x:rect.width/2.0,y:rect.height/2.0),radius:rect.height/2.0+5.0,startAngle:.degrees(0),endAngle:.degrees(360.0*Double(pct)),顺时针方向:false)returnp.strokedPath(.init(lineWidth:10,dash:[6,3],dashPhase:10))}}structLabelView:View{letpct:CGFloatvarbody:someView{Text("\(Int(pct*100))%").font(.largeTitle).fontWeight(.bold).foregroundColor(.white)}}}正如您在示例代码中看到的那样,ArcShape无法设置动画,因为修改器已多次创建形状具有不同的pct值。动画渐变在实现渐变动画时可能会遇到一些限制。例如,你可以对起点和终点进行动画处理,但不能对渐变颜色进行动画处理。使用AnimatableModifier可以避免这种情况。很容易实现这个功能,在此基础上,可以实现更复杂的动画,如果我们需要插入中间色,只需要计算RGB值的平均值即可。另外注意修改器假设输入的颜色数组都包含相同数量的颜色。完整代码如文末链接中的例11。关键代码如下:structAnimatableGradient:AnimatableModifier{letfrom:[UIColor]letto:[UIColor]varpct:CGFloat=0varanimatableData:CGFloat{get{pct}set{pct=newValue}}funcbody(content:Content)->someView{vargColors=[Color]()foriin0..Color{guardletcc1=c1.cgColor.componentselse{返回Ccolor(c1)}guardletcc2=c2.cgColor.componentselse{returnColor(c1)}letr=(cc1[0]+(cc2[0]-cc1[0])*pct)让g=(cc1[1]+(cc2[1]-cc1[1])*pct)让b=(cc1[2]+(cc2[2]-cc1[2])*pct)返回Color(red:Double(r),green:Double(g),blue:Double(b))}}更多文本动画在这个例子中,我们将再次实现一个文本动画,但是一步一步,一次放大一个字符。完整的代码显示在文章末尾链接中的示例12中。关键代码如下:structWaveTextModifier:AnimatableModifier{lettext:StringletwaveWidth:Intvarpct:Doublevarsize:CGFloatvaranimatableData:Double{get{pct}set{pct=newValue}}funcbody(content:Content)->someView{HStack(spacing:0){ForEach(Array(text.enumerated()),id:\.0){(n,ch)inText(String(ch)).font(Font.custom("Menlo",大小:self.size).bold()).scaleEffect(self.effect(self.pct,n,self.text.count,Double(self.waveWidth)))}}}funceffect(_pct:Double,_n:Int,_total:Int,_waveWidth:Double)->CGFloat{letn=Double(n)lettotal=Double(total)returnCGFloat(1+valueInCurve(pct:pct,total:total,x:n/total,waveWidth:waveWidth))}funcvalueInCurve(pct:Double,total:Double,x:Double,waveWidth:Double)-&g吨;Double{letchunk=waveWidth/totalletm=1/chunkletoffset=(chunk-(1/total))*pctletlowerLimit=(pct-chunk)+offsetletupperLimit=(pct)+offsetguardx>=lowerLimit&&xsomeView{让n=self.number+1让tOffset:CGFloat=getOffsetForTensDigit(n)让uOffset:CGFloat=getOffsetForUnitDigit(n)让你=[n-2,n-1,n+0,n+1,n+2].map{getUnitDigit($0)}letx=getTensDigit(n)vart=[abs(x-2),abs(x-1),abs(x+0),abs(x+1),abs(x+2)]t=t.map{getUnitDigit(Double($0))}letfont=Font.custom("Menlo",size:34).bold()returnHStack(alignment:.top,spacing:0){VStack{Text("\(t[0])").font(字体)Text("\(t[1])").font(字体)Text("\(t[2])").font(字体)Text("\(t[3])").font(字体)Text("\(t[4])").font(font)}.foregroundColor(.green).modifier(ShiftEffect(pct:tOffset))VStack{Text("\(u[0])").font(font)Text("\(u[1])").font(font)Text("\(u[2])").font(font)Text("\(u[3])").font(font)Text("\(u[4])").font(font)}.foregroundColor(.green).modifier(ShiftEffect(pct:uOffset))}.clipShape(ClipShape()).overlay(CounterBorder(height:$height)).background(CounterBackground(height:$height))}funcgetUnitDigit(_number:Double)->Int{returnabs(Int(number)-((Int(number)/10)*10))}funcgetTensDigit(_number:Double)->Int{返回一个bs(Int(number)/10)}funcgetOffsetForUnitDigit(_number:Double)->CGFloat{return1-CGFloat(number-Double(Int(number)))}funcgetOffsetForTensDigit(_number:Double)->CGFloat{ifgetUnitDigit(number)==0{return1-CGFloat(number-Double(Int(number)))}else{return0}}}动画文字颜色通常通过.foregroundColor()添加到动画中,但是它没有用于文字动画时的效果。我不知道缺少什么配置或为什么。我使用以下方法为文本动画添加颜色。完整代码在文章末尾的链接中,如示例14。关键代码如下:structAnimatableColorText:View{letfrom:UIColorletto:UIColorletpct:CGFloatlettext:()->Textvarbody:someView{lettextView=text()returntextView.foregroundColor(Color.clear).overlay(Color.clear.modifier(AnimatableColorTextModifier(from:from,to:to,pct:pct,text:textView)))}structAnimatableColorTextModifier:AnimatableModifier{letfrom:UIColorletto:UIColorvarpct:CGFloatlettext:TextvaranimatableData:CGFloat{get{pct}set{pct=newValue}}funcbody(content:Content)->someView{returntext.foregroundColor(colorMixer(c1:from,c2:to,pct:pct))}//这是颜色插值的非常基本的实现//在两个值之间。funccolorMixer(c1:UIColor,c2:UIColor,pct:CGFloat)->Col或{guardletcc1=c1.cgColor.componentselse{returnColor(c1)}guardletcc2=c2.cgColor.componentselse{returnColor(c1)}letr=(cc1[0]+(cc2[0]-cc1[0])*pct)让g=(cc1[1]+(cc2[1]-cc1[1])*pct)让b=(cc1[2]+(cc2[2]-cc1[2])*pct)returnColor(red:Double(r),green:Double(g),blue:Double(b))}}}版本相关问题从上面的介绍可以看出AnimatableModifier非常强大,但仍然存在一些问题另外,在Xcode和iOS/macOS的某些版本中,App会在启动时崩溃。而且在部署的时候,正常的开发编译不会出现这种情况。dyld:Symbolnotfound:_$s7SwiftUI18AnimatableModifierPAAE13_makeViewList8modifier6inputs4bodyAA01_fG7OutputsVAA11_GraphValueVyxG_AA01_fG6InputsVAiA01_L0V_ANtctFZReferencedfrom:/Applications/MyApp.app/Contents/MacOS/MyAppExpectedin:/System/Library/Frameworks/SwiftUI.framework/Versions/A/SwiftUI例如,如果App在Xcode11.3在macOS10.15.0上部署,在macOS10.15.0上执行时,出现“Symbolnotfound”错误。但是,在macOS10.15.1上运行相同的可执行文件工作正常。翻译自TheSwiftUILab'sAdvancedSwiftUIAnimations–Part3:AnimatableModifier本文完整的示例代码可以在以下位置找到:https://gist.github.com/swiftui-lab/e5901123101ffad6d39020cc7a810798示例8所需的图片资源。从这里下载:https://swiftui-lab.com/?smd_process_download=1&download_id=916