Animating NSLayoutAnchor Constraints and the Pitfalls Involved
May 21, 2016
This post has been updated for Swift 4.0. You can find it here
Correctly laying out an iPhone application sometimes feels like the 20% of a project that takes 80% of your effort/time. That's why when Apple hit us with NSLayoutAnchor in iOS 9 I jumped on the opportunity to make life easier. If you haven't spent time using NSLayoutAnchor check out my Getting Started with NSLayoutAnchor (Swift 3.1) article real quick and then come back. Today I wanna show you how to animate those constraints and best practices I've come across.
Animating NSLayoutAnchor Constraints
Check it out. That's it. When you set new constraints on a view or object you just simply add these three lines of code and BAM you've got an animation. You can use any variation of .animateWithDuration as long as you make sure to call .layoutIfNeeded() inside the animation block on the parent view that holds the view you're wanting to animate.
Here's a more complete example of what that might look like in an application (This is all done in viewDidLoad).
- Create and instantiate a view with constraints for width and height.
- Place constraints needed to animate your view inside an array that holds NSLayoutConstraint as elements.
- Activate your constraints.
- Animate your constraints.
ERROR: Unable to simultaneously satisfy constraints.
What does that even mean "Unable to simultaneously satisfy constraints"? It means you've added some extra constraints that aren't playing nicely with some constraints you previously set (or didn't). If you ever see this error the FIRST thing you should look for is if you wrote this line of code in ViewDidLoad.
It's set to true by default and that means the compiler is trying to help you make constraints but since you decided you'd do it yourself the compiler is confused.
If you remembered to do that then my next suggestion is to check if you deactivated your constraints before adding new ones. The object is simply a soldier trying to satisfy all your orders but don't expect it to do it all at once.
- Deactivate all constraints on your view using the property .constraints that returns an array of NSLayoutConstraint.
- Activate your constraints.
Why is my view disappearing?
If you've been following along you might've noticed your view is vanishing like all your friends when you ask for a ride. This is where constraints can become a headache. When you deactivated all your views constraints you also deactivated the constraints telling it how wide and tall it is. So how do you tell your view which constraints to keep and get rid of?
There are a few ways to do this but I'm gonna show you the way I've found to be easiest.
- Create the constraints you'd like the view to have when its first drawn.
- Create the constraints for the view to animate to after a certain event happens.
In this new example I create and activate the constraints for width and height outside of my array of constraints I want to animate. Next, I create variables to hold an array of NSLayoutConstraint so that I can cherry pick which constraints to add and lose. No more magically disappearing views!
One more optimization...
It started to bother me how often i was writing NSLayoutConstraint to deactivate and activate constraints in my ViewControllers so I made this handy variable to handle it for me.
You can use this variable to automatically deactivate and activate new constraints when you set them to this variable. Using property observer willSet to deactivate the old constraints that activeConstraints was holding and then in didSet to active the new constraints activeConstraints is set to. With that addition I can update my previous example to this.
Pretty awesome huh? You've got a great way to keep up with your currently active constraints and reduced the repetitive code we had before. All I ask is you don't abuse this power ;).
If you'd like to see a complete project here it is on GitHub for all to see. Feel free to let me know any improvements you've made when animating constraints.
If you've read this far thank you very much! I really hope it was worth the read and that you'll share/like/heart and let others know it would be worth their time too! Please leave me a comment, I'd love to talk with anyone who enjoyed this post! Auf wiedersehen!