structAnimatableGradient:AnimatableModifier{let from:[UIColor]let to:[UIColor]var pct:CGFloat=0var animatableData:CGFloat{get{ pct }set{ pct = newValue }}funcbody(content:Content)->someView{var gColors =[Color]()for i in0..<from.count {
gColors.append(colorMixer(c1: from[i], c2: to[i], pct: pct))}returnRoundedRectangle(cornerRadius:15).fill(LinearGradient(gradient:Gradient(colors: gColors),
startPoint:UnitPoint(x:0, y:0),
endPoint:UnitPoint(x:1, y:1))).frame(width:200, height:200)}// This is a very basic implementation of a color interpolation// between two values.funccolorMixer(c1:UIColor, c2:UIColor, pct:CGFloat)->Color{guardlet cc1 = c1.cgColor.components else{returnColor(c1)}guardlet cc2 = c2.cgColor.components else{returnColor(c1)}let r =(cc1[0]+(cc2[0]- cc1[0])* pct)let g =(cc1[1]+(cc2[1]- cc1[1])* pct)let b =(cc1[2]+(cc2[2]- cc1[2])* pct)returnColor(red:Double(r), green:Double(g), blue:Double(b))}}
四、更多文本动画
再次实现一个文本动画,逐步进行,一次放大一个字符,如下所示:
关键代码如下(完整代码请参考文末的完整示例的示例 12):
structWaveTextModifier:AnimatableModifier{let text:Stringlet waveWidth:Intvar pct:Doublevar size:CGFloatvar animatableData: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", size: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{let n =Double(n)let total =Double(total)returnCGFloat(1+valueInCurve(pct: pct, total: total, x: n/total, waveWidth: waveWidth))}funcvalueInCurve(pct:Double, total:Double, x:Double, waveWidth:Double)->Double{let chunk = waveWidth / total
let m =1/ chunk
let offset =(chunk -(1/ total))* pct
let lowerLimit =(pct - chunk)+ offset
let upperLimit =(pct)+ offset
guard x >= lowerLimit && x < upperLimit else{return0}let angle =((x - pct - offset)* m)*360-90return(sin(angle.rad)+1)/2}}extensionDouble{var rad:Double{returnself*.pi /180}var deg:Double{returnself*180/.pi }}
五、计数器动画
如下所示,如何创建一个计数器动画:
其实很简单,就是为每个数字使用 5 个 Text 视图,并通过 .spring() 动画上下移动它们,此外还需要使用 .clipshape() 修饰符,来隐藏绘制边框外的部分。
structMovingCounterModifier:AnimatableModifier{@Stateprivatevar height:CGFloat=0var number:Doublevar animatableData:Double{get{ number }set{ number = newValue }}funcbody(content:Content)->someView{let n =self.number +1let tOffset:CGFloat=getOffsetForTensDigit(n)let uOffset:CGFloat=getOffsetForUnitDigit(n)let u =[n -2, n -1, n +0, n +1, n +2].map {getUnitDigit($0)}let x =getTensDigit(n)var t =[abs(x -2),abs(x -1),abs(x +0),abs(x +1),abs(x +2)]
t = t.map {getUnitDigit(Double($0))}let font =Font.custom("Menlo", size:34).bold()returnHStack(alignment:.top, spacing:0){VStack{Text("\(t[0])").font(font)Text("\(t[1])").font(font)Text("\(t[2])").font(font)Text("\(t[3])").font(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{returnabs(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}}}
structAnimatableColorText:View{let from:UIColorlet to:UIColorlet pct:CGFloatlet text:()->Textvar body:someView{let textView =text()return textView.foregroundColor(Color.clear).overlay(Color.clear.modifier(AnimatableColorTextModifier(from: from, to: to, pct: pct, text: textView)))}structAnimatableColorTextModifier:AnimatableModifier{let from:UIColorlet to:UIColorvar pct:CGFloatlet text:Textvar animatableData:CGFloat{get{ pct }set{ pct = newValue }}funcbody(content:Content)->someView{return text.foregroundColor(colorMixer(c1: from, c2: to, pct: pct))}// This is a very basic implementation of a color interpolation// between two values.funccolorMixer(c1:UIColor, c2:UIColor, pct:CGFloat)->Color{guardlet cc1 = c1.cgColor.components else{returnColor(c1)}guardlet cc2 = c2.cgColor.components else{returnColor(c1)}let r =(cc1[0]+(cc2[0]- cc1[0])* pct)let g =(cc1[1]+(cc2[1]- cc1[1])* pct)let b =(cc1[2]+(cc2[2]- cc1[2])* pct)returnColor(red:Double(r), green:Double(g), blue:Double(b))}}}
dyld:Symbol not found:_$s7SwiftUI18AnimatableModifierPAAE13_makeViewList8modifier6inputs4bodyAA01_fG7OutputsVAA11_GraphValueVyxG_AA01_fG6InputsVAiA01_L0V_ANtctFZ
Referenced from:/Applications/MyApp.app/Contents/MacOS/MyAppExpectedin:/System/Library/Frameworks/SwiftUI.framework/Versions/A/SwiftUI