Animating Constraints Using iOS 10’s New UIViewPropertyAnimator
In Session 216 during WWDC 2016, Apple announced a fairly undervalued update to UIKit animation that blows the standard UIView.animate(), keyframe animations, and even Facebook’s POP framework completely out of the water. It’s called UIViewPropertyAnimator and it allows for highly interactive and customizable animations. You can read more about it here, and learn the basics here.
After playing around with UIViewPropertyAnimator and understanding all the different rules that apply to its stopped and inactive state, I tried taking things to the next level by animating constraints. I found this to be extremely powerful since you can control the progress of an animation using UIViewPropertyAnimator’s .fractionComplete() method.
I’d like to share an example of constraint animation with this interactive burger menu, inspired by Javi Perez’s Nominazer app.
I first use an instance of UIViewPropertyAnimator to converge the three lines in the burger into one line, using a pan gesture recognizer to update .fractionComplete()
When the user ends the pan gesture, a UIView.animate() block forms an ‘X’.
The user can then converge the X’s lines back into one line with a new UIViewPropertyAnimator.
Finally, a UIView.animate() block is used to form the original three-lined burger.
As you can see, UIKit’s older animation methods can be used in conjunction with UIViewPropertyAnimator to create seamless interactions with the UI.
You can download the code for this interactive burger on my GitHub page. (Beware, it has a lot of personal notes scribbled all over it.)
Back to the topic on hand: animating constraints.
Note: I’m using SnapKit’s DSL for constraints, I highly recommend it.
Above is a snippet of code from the interactive burger class that remakes the constraints of the top and bottom line in the burger in order to move from their original position (relative to their container view) to the center, seamingly converging all three lines into one.
But what if the user lifts their finger off the screen in the middle of a pan gesture, in order to cancel the animation? In that case, all we have to do is reverse the animation above, which you would think would “reset” our three lines back to their original position (in other words, their original constraints). Right?
Unfortunately, when the user tries panning again, calling a new instance of UIViewPropertyAnimator, then the app crashes with this error:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An animator (<UIViewPropertyAnimator(0x6000001747c0) [inactive] interruptible>) must have at least one animation block to start!'
After a grueling two hours of scavenging Apple’s documentation for answers and playing around with my code, I figured out that after playing the animation of setting constraints in reverse, iOS doesn’t internally reset any constraints back to “normal”, so you have to manually set your views’ constraints back to normal in the reversed animation’s completion block. That’s why I was getting an error - because there was nothing to animate if my UIViewPropertyAnimator was trying to animate changes to constraints that were already set to their final state.
Again you can download the entire source code for this project here.