We have animated one turn of the spinner in After Effects and will use Xcode to loop the animation.
We want the null to rotate independently and we also want the colors to shift independently. We do, however, want the trim path end and the z rotation of the layer to sync up and repeat in the same rythm. So we are animating the end of a trim path from 85% to 1% to 85% and the rotation from 0 to 0 to 360°. The extra zero keyframe in the beginning ensures that the animations are of the exact same length and will therefore repeat together.
var spinnerAnimation : SLCoreAnimation!
let r = SLReader()
var animationInfo : SLAnimationInformation?
do {
animationInfo = try r.parseFileFromBundle("spinner.sqa")
} catch {
print("error \(error)")
}
This code sets up a SLReader which reads our sqa file and spits out a SLAnimationInformation object.
if animationInfo != nil {
let a = SLCoreAnimation.init()
a.buildDelegate = self
a.buildWithInformation(animationInfo!)
a.play()
self.frame = a.frame
self.layer.addSublayer(a)
}
For the spinner we are using a SLCoreAnimation rather than SLSquallAnimation because they are more resilient to stalls on the main thread and their interface allows for independent property looping.Before we build the animation we set the our spinner class as the build delegate for this SLCoreAnimation. Our spinner adheres to the SLCoreAnimationBuildDelegate protocol.
After we build the animation we set the frame of the containing layer to the frame of the animation. The frame of the animation is determined by the composition size in After Effects.
The Spinner class implements the shouldAddAnimations method. For each animated layer in After Effects the delegate gets a callback with a CAAnimationGroup, a CALayer and a String.
func shouldAddAnimations(group: CAAnimationGroup, toLayer layer: CALayer, withName name:String) -> CAAnimationGroup? {
print("AE layer \(name)")
for ani : CAKeyframeAnimation in group.animations! as! [CAKeyframeAnimation] {
print("\tanimating \(ani.keyPath)")
}
return group;
}
Output:
AE Layer: SpinnerPath
animating Optional("strokeEnd")
animating Optional("strokeColor")
AE Layer: SpinnerGroup
animating Optional("transform.rotation.z")
AE Layer: Null
animating Optional("transform.rotation.z")
It is always a good idea to log the values the build delegate get passed before trying to manipulate them.
func shouldAddAnimations(group: CAAnimationGroup, toLayer layer: CALayer, withName name:String) -> CAAnimationGroup? {
switch name {
case "Null":
for ani in group.animations! as! [CAKeyframeAnimation] {
if ani.keyPath == "transform.rotation.z" {
ani.repeatCount = 10000.0
}
}
case "SpinnerPath":
for ani in group.animations! as! [CAKeyframeAnimation] {
if ani.keyPath == "strokeColor" || ani.keyPath == "strokeEnd" {
ani.repeatCount = 10000.0
}
}
case "SpinnerGroup":
for ani in group.animations! as! [CAKeyframeAnimation] {
if ani.keyPath == "transform.rotation.z" {
ani.repeatCount = 10000.0
}
}
default:
break
}
group.duration = 10000.0
}