Beyond Constraints: Crafting Advanced iOS Animations with Auto Layout

Development iOS

How we think about design at Savvy Apps encouraged us to develop a new animation technique that mixes old-school view animations with Auto Layout. Our technique bypasses the restrictions placed on developers when they try to animate using Auto Layout. Most Auto Layout animation guides say basically the same thing: update constraints and animate. For more advanced animations though, just updating the constraints would be nearly impossible.

By relying only on constraints, you're restricting yourself on the types of animations you can achieve in your app. This article dives into how to get around the limitations of constraints by using Auto Layout in new ways.

Auto Layout and Animation

Auto Layout is a powerful tool that controls all view layouts in iOS. Even if you don’t use it directly, the OS automatically transforms the view’s properties, like the frame, into constraints before laying out the views.

At Savvy Apps we use Auto Layout in all of our projects because its visual nature makes it easy to digest when working on a view, especially one you didn’t create. One of the downsides of Auto Layout, however, is that it’s very rigid. Since animations are important in iOS, we've had to find different ways to implement various kinds of animations while using Auto Layout.

Animating with Constraints

Using constraints is the simplest way of animating with Auto Layout. As there are many tutorials about this topic already, (including this handy resource) we won't go too in-depth here. The gist is that you modify the constraints in your view, either by changing the constant property, creating or deleting constraints, and calling layoutIfNeeded() inside an animation block, like this:

// Constraints can be either connected through Interface Builder as outlets
// or created at runtime
var heightConstraint: NSLayoutConstraint!
...
heightConstraint.constant = 100
UIView.animate(withDuration: 0.25) {
    self.view.layoutIfNeeded()
}

The big downside of this approach is that more advanced animations require keeping track of multiple constraints, which can be tedious and hard to maintain. Consider the view to the right:

The more advanced your animation, the more constraints you have to keep track of and manage.

In this view, imagine you want to animate the red box to a different position, wherein each position is part of the layout and affects other views. You usually need four constraints to lay out a view, which means you now have eight constraints to track, four for each position.

This view, however, also affects other views. Let's say it interacts with two in each state. This means we need to track two more constraints per view. This brings us to a total of 12 constraints spread around your Storyboard, half of which have to be disabled to prevent layout errors, and all have to be connected to outlets in your controller. If you want to animate a second view at the same time, this turns into 24 constraints to manage. As you can see, the complexity increases very quickly.

Animating with Transform

Using the transform property of a UIView is a great way to quickly create simple animations that don’t modify the layout of the screen. CGAffineTransforms support translation, rotation, scale, and any combination of them. The best case for transform animations are with views that don’t change position but do have an animation. If you are having a hard time picturing it, imagine a view that won’t change its position once it’s in place, but will slide in from the top when the view first appears.

To animate the transition, we can use the transform to translate the view off the screen before it appears and onto the screen once it appears.

func viewWillAppear(_ animated: Bool){
    …
    contentView.transform = CGAffineTransform(translationX: 0, y: -contentView.frame.height)
}

func viewDidAppear(_ animated: Bool) {
    …
    UIView.animate(withDuration: 0.25) {
        contentView.transform = .identity
    }
}

You can also mix other properties like the alpha or scale and rotate the view. There are many other use cases for this kind of animation, like wiggling icons, making a button bounce when it’s tapped, or shaking a text field if its contents don’t pass the required validation.

Making More Advanced Auto Layout Animations

It wouldn't be ideal to create this animation using the constraints or transform methods.

If you look at the player animation we did in The Cato Institute app, CatoAudio, you might have trouble picturing how you would use any of the previously mentioned methods to implement it. The answer is that you probably wouldn't be able to. The constraints method wouldn't work because the elements we want to animate affect the layout of the rest of the elements in each state. Modifying their constraints would affect the rest of the elements, and we don't want that. The transform method we mentioned is more suited for simpler animations. While we might be able to implement this animation using a transform, the process would be very similar to what we are going to explain with the added difficulty of figuring out the exact transform we need.

In this animation there are three animated elements that exist in both compact and expanded states: the playback button, the artwork, and the playback bars. These elements, especially the playback button and the image view, play a very big role in each layout. If we tried to use constraints (as mentioned earlier) to perform this transition, we would have to keep track of dozens of constraints. Even if we still wanted to go that route, we would lose the ability to have the transition follow the user input, among other things.

The way we implemented this animation is actually simpler than it seems. First, we created a container view in our controller for each state. Each container had a static layout with all the elements that wouldn’t be animated. In each container we created invisible views that represented the frame of each animated element in that state. These views helped create the layout like the element was there. Then we added the actual elements to our main view. These views had no constraints, since we set their frame manually. With all the views in place, we connected it all by having three IBOutlets for each element: compactReferenceView, expandedReferenceView, and elementView.

A look at the way the view is set up in Interface Builder.

When performing the animation we used a lerp function to get the frame of the element in the current state and set it to the element. We calculated this frame whenever the user moved their finger, or by using a CADisplayLink when they triggered the automatic animation. Lerp is a linear interpolation function that helps you define a value between two values given a progress between 0 and 1. It’s defined by this formula x = x0 + (x1 - x0) * p. Because of its nature, a lerp function is linear. To make the animation look better we applied an animation curve to the progress variable before passing it into the lerp function. The result is what you can see in the app.

Concluding Note

To help you adopt this technique for your own apps we prepared a project that further demonstrates how to use this animation method. Please note that this sample project does not include all the bells and whistles that the final polished animation in the Cato Institute app has, like following the user's finger or custom animation curves. If you want to try out this technique for yourself, check out the sample project on GitHub. Please drop us a line if you end up using this technique in your own apps.

Emilio is an iOS developer with some experience in game development and design. He enjoys bringing concepts from game development into applications to improve user interactivity.

You made it this far so...