Saturday March 24, 2012
Core Animation is one of the finer frameworks in the SDK, it abstracts away a lot pesky details in a declaritive way. What declaritive means here is that you tell a CALayer to, for instance, go somewhere by changing its
position property and it’ll animate it’s way there. You can also build up your own animations with the varius CAAnimation subclasses, such as CABasicAnimation and CAKeyframeAnimation. With these animation classes you set various properties (to/from values, keyframe values) along with things such as the length and the timing function, but I became curious on how one would stop an animation “mid-flight” or change the speed once it has started. Turns out it’s fairly easy.
In Core Animation, time is modelled in a tree-like fashion, with the speed of one animation being relative to the speed of its parent animation. What that means that if you set a child animation to take 10 seconds it’ll still take 10 seconds, however, if you change the speed of the parent animation any child animations will will change its speed relative to that. Both CAAnimation and CALayer’s themselves adopt the CAMediaTiming protocol which models this time space relationship.
Like Apple describes in this Technote, in order to pause an animation you set the speed to 0.0 and its timeoffset to the current time (relative to its parent time). It may seem counter intuitive that just settings the speed to zero isn’t enough, but if you only did that then it would simply return to its initial starting point as we don’t freeze the time and the
speed property simply maps the child animations time space from its parent time space.
layer.speed = 0.0; layer.timeOffset = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
Starting it involves setting the speed to 1.0, but also finding the current
timeOffset (where we paused it), resetting it, and setting the
beginTime based on the time difference between now and when it was paused so the animation will start exactly from where it was paused.
layer.speed = 1.0; CFTimeInterval pausedTime = layer.timeOffset; layer.timeOffset = 0.0; layer.beginTime = 0.0; CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime; layer.beginTime = timeSincePause;
Slowing down time or speeding it up becomes easy once we learn how Core Animation maps time spaces:
layer.timeOffset = [layer convertTime:CACurrentMediaTime() fromLayer:nil]; layer.beginTime = CACurrentMediaTime(); layer.speed = 0.5;
Chaning the speed of an animation means we have to change the speed obviously, but we also have to remap the time space into the current time. So we set the timeOffset to the layer’s current time and the beginTime to our global time.
I haven’t actually had a use for this in a real app, as it was one of those “I wonder how…” moments, but it might be useful for someone out there.