There’s new behavior involving
animateWithDuration: in iOS 8 that can help make certain “interruptible” animations a lot smoother.
The classic use case is a togglable animation that can be reversed mid-flight, like a drawer that opens or closes when a button is tapped. The gist of the change is that in iOS 8, when calls to
animateWithDuration: overlap, any previously scheduled, in-flight animations on the same properties will no longer be yanked out of the view’s layer, but instead be allowed to finish even as the new animation takes effect and is blended with the old one(s). (For properties that adopt this additive animation behavior, it will happen whether or not you use the
Consider the example of a view whose
center.y is being animated from 0 to 100 over 1 second using
animateWithDuration:. Halfway though, at the 0.5-second mark, a second
animateWithDuration: block, also with a 1-second duration, sends the view back to 0.
In iOS 7 and earlier, using the
UIViewAnimationOptionBeginFromCurrentState option and the default animation curve (
UIViewAnimationOptionCurveEaseInOut), the complete animation would look like this:
At 0.5 seconds, when the second animation block is called, a new
CABasicAnimation gets added to the animating view’s
CALayer with the key
position and the keypath
position, replacing the previous one still in flight. The starting position for the new animation is animating view’s current position — that is, the position of its layer’s
The resulting 1.5-second animation is continuous, in the sense that the view does not jump to a new position. But the speed changes abruptly in both magnitude and direction at 0.5 seconds. Not so pretty.
In iOS 8, however, the same sequence produces a very different animation — see the dotted blue line below:
At 0.5 seconds, a second
CABasicAnimation is added, but with a different key than the first one — the system happens to use
position-2 — and both animations are allowed to run their course. Because both animations have the
additive property set to
YES, the position changes are added together. (The red and yellow lines don’t add up to the blue line because the animation values are relative — to the model position — and not absolute; the actual math involves positive and negative values that offset each other.)
The result is a smooth curve that, in this example, peaks at 0.75 seconds, as the animating view overshoots and then reverses itself.
You can continue to add animations in rapid succession using
animateWithDuration:, and the layer will accumulate additive animations with keys like
position-4, etc. The visual effect is generally quite smooth and natural.
This new behavior isn’t so pretty, however, for animations using a linear timing function. In this simple example, if the
UIViewAnimationOptionCurveLinear option were used instead of the default ease-in-ease-out, the additive animations would cancel eachother out, resulting in the view being “frozen” until the previous animation ended. This definitely looks weird. See the 0.5-second plateau in the blue curve:
Since you apparently can’t opt out of additive animations in iOS 8, you’d need to do a bit of extra work to restore the old, non-additive behavior. In the simplest case, you could simply rip out any in-flight animations yourself before the new call to
animateWithDuration:, making sure to manually reset the layer’s position to sync up with the presentation layer. Something like this, right before the new animation block, seems to work:
CALayer *presLayer = (CALayer *)self.animatingView.layer.presentationLayer; self.animatingView.layer.position = [presLayer position]; [self.animatingView.layer removeAllAnimations];
In most cases, though, I assume the additive animations will be welcome as an easy way to smooth out overlapping transitions.
Check out this WWDC 2014 video for more on additive animations in iOS 8.