iOS Animations with Auto Layout

In this presentation from GOTO Conference CPH 2015, Marin shows how to create animations in UIKit with Swift that work with Auto Layout UIs. He looks into using existing constraints in animations, as well as replacing those constraints to create better animations. He also touches on how and when to trigger animated and static layout updates, discussing how and why those animations work like a breeze. Then a bonus! - a demo of animating with iOS 9’s UIStackView.


Introduction (0:00)

My name is Marin Todorov and I’m an independent developer and author. I split my time between my own studio for making iPhone apps and a website called raywenderlich.com. At the beginning of 2015, I published a book called iOS Animations by Tutorials, a fantastic book about animations on iOS. I also write a monthly animations newsletter. I’m here today to talk about Auto Layout and stack views.

Animations are fantastic in iOS 7, 8 and 9 because we no longer have rich user interfaces that have wood backgrounds or steel concrete gutters. Today, iOS is mostly text and color. You have to find new ways to put your brand up in the front of everyone and make your app distinguishable from the others. For example, this screenshot could be one of Apple’s built-in apps, or it could be a custom app that somebody invested hundreds of hours designing.

Animations are an amazing way to make your app unique. The above example is the built-in contacts app in iOS, but it may have been difficult to tell because it’s about white space, text and color. If you have ever written an app on iOS you have done at least one animation.

Most animation tutorials start with the red square. The square moves along with a single line of code using animateWithDuration. You specify how long you want the animation to go and define the final values for the target animation. In this example, the UIKit API will take the current values and current position of the red square and create an animation over one second moving it on the x-axis to value of 200—looking something like this.

UIView.animateWithDuration(1.0, animations: {
  redSquare.center.x = 200.0
})

This was fantastic with iOS 3, 4 and 5, but then we were introducted to new screens. Things changed. Moving the red square to the point 200 on the x-axis didn’t mean much anymore. Before having different screen sizes, you knew where the screen started; point 0. Then, you moved it 200 points to the right. With the iPhone 6, 6+ or 5S, for example, you no longer know what it means to move to point 200. It might be close to the edge of the screen, straight in the center, or anywhere.

Before going further, I want to say it’s better to understand how things work and why they happen so you know what to do the next time you encounter a similar problem. Do not run off to Stack Overflow and accept the first answer without an understanding of the solution.

Auto Layout is Different Than Pre–Auto Layout (6:17)

Auto Layout is different than pre–Auto Layout. I’ll demonstrate with a quick demo. My hope is that you’ll know how create animations with Auto Layout by the end, or at least know how to approach them.

Here is my storyboard in a present day application. I’m using Auto Layout. I have a login screen for an imaginary app and I have a username and password field at the top. I want to animate them towards the center of the screen when a user opens the app. I laid out everything in Interface Builder, centered the fields and put constraints on username and password.

Then, let’s imagine I go to Stack Overflow to see how I can create an animation. Super Awesome Ninja Dev has answered my question, “Call your animation with duration and then change the center property and that should work.” I go to my code and put in UIView.animationWithDuration, with half a second duration. I have two outlets, username and password. I’m going to try to animate their center property. They’re still not entirely visible because I didn’t make them fully opaque, so I’m going to make them opaque as well.

override func viewDidAppear(animated: Bool) {
  super.viewDidAppear(animated)

  UIView.animateWithDuration(0.5, animations: {
    self.fldPassword.center.y += 200
    self.fldUsername.alpha = 1.0
    self.fldPassword.alpha = 1.0
  })

  UIView.animateWithDuration(0.5, delay: 0.1, options: [], animations: {
    self.fldUsername.center.y += 200
    }, completion: nil)
}

I run into a problem when I enter the username. Everything jumps up as soon as I focus on the text field. Two things are happening. First, the fields repositioned to where they were originally. Second, they did not fade out as they were originally. Auto Layout makes your layout for you automatically, but here you went over its head and changed the position of these views manually. That’s not good. The first moral of this talk is not everything about animation changes with Auto Layout. It’s fine if you animate the alpha or a background call or something like that—that still works the old way. What does not work in the old way is changing the center manually.

The question is why did it jump back like this when I tapped into the text field? When you lay down buttons, labels, images, etc. in Interface Builder and set up your constraints, you’re attaching a list of constraints on those views that explain the relationship between them. In the end, everything depends on the size of the screen. For example, when the keyboard jumps up or when a call is incoming, the size of the container or view controller changes.

NSLayoutConstraint subclasses NSObject. It’s not a kind of visual class. It doesn’t have the tremendous logic behind it; it’s just a model. It contains few properties that define an equation so that you can set rules. For example, buttons should be always centered. It’s important to know that the constraints you design in Interface Builder are models—data objects that contain few pieces of information. They don’t draw anything with UIKit or move your views, but Auto Layout does. When things need to appear on screen or find their spot on the screen, Auto Layout grabs the list of constraints, solves the equations and finds where the views should appear on screen. There are all kind of constraints. There’s width, height, center X, margin Y, ratio between width and height, vertical space and vertical to margins, and more. But, when Auto Layout solves all these rules that you set up, it changes the position of your views and their size. That’s all it does.

Auto Layout reacts to internal and external changes to a layout. Examples of internal changes are changes in constraints, removal of constraints or a changes in code. An example of an external change is rotating the device, which changes the size of the view controller.

Let’s say that you push a view controller when you load up an app. First, the app loads the view from the story board. Auto Layout needs to solve the layout so that it knows where to show things on screen. It solves all your constraints and finds the position of all the views. However, if you tap on a text field, for example, the keyboard will pop up and change the size of the view controller margins. This will trigger another pass on the layout and Auto Layout will have the orientation change. Auto Layout will again get the list of constraints, calculate them based on the container size, and then reposition.

How can we do proper animations? Any changes made within the following closure will be animated.

UIView.animateWithDuration(1.0, animations: {
  view.center.x -= 10
})

There’s a list of the animatable properties such as alpha, background caller, position, size, etc. that you can change within the closure and they will be automatically automated by UIKit. Now, we need to connect a pass on the Auto Layout that takes all the constraints, recalculates them and changes center and bounds which are animatable properties. We’re getting really close to what was happening before, but we need to change the constraints and force a pass on the layout from within the animation block. Let me quickly show how to do it from code.

This my next example for showing constraints and how to animate them. I have a little to-do list app and I have this top bar that I want to dynamically resize. To show you the premise, let me open the story board file. There’s one view on the top and one table view on the bottom. The top view is pinned to the top, the table view is pinned to the bottom, and they’re pinned to each other. I have the height constraint of this one that says it’s going to be height 60 points.

There are two ways to animate constraints. The first is when you want to change the constant of a constraint. You can do this by opening the properties and editing the height, for example. I know which constraint I want to animate so I’m going to go to my view controller and I’m going to make an outlet. Since constraints are something that you lay down in Interface Builder, you can also make outlets and use them to access constraint in real time. I’m going to put in an IBOutlet called menu height, and it’s going to be of type NSLayoutConstraint.

@IBOutlet weak var menuHeight: NSLayoutConstraint!

Then, any other button view, text view, etc. that you’re used to doing by using by outlets, you can go to the story board, find the height constraint, come to the Connections Inspector and drag from the new referencing outlet back to view controller, connecting your outlet to the constraint.

@IBAction func actionToggleMenu(sender: AnyObject) {

    menuHeight.constant = 200

}

I changed the constraint, and this is a change that will not be undone randomly because now the constraint says that it’s going to be a height of 200. Since we can control the constraint now, the animations are really easy to do. Wrap it up with an animateWithDuration call or a spring animation. I’m going to use a spring animation. This is the default UIKit API, again. Nothing really crazy going on.

UIView.animateWithDuration(1.0, delay: 0.0,
  usingSpringWithDamping: 0.33, initialSpringVelocity: 0,
  options: [], animations: {

    self.view.layoutIfNeeded()

  }, completion: nil)

What I said on my slide and I’m going to stick to it, was changing the constraints anywhere in code, and then force a pass on that Auto Layout from within the animation block. The layout pass is the one that changes the center and the bounds and so forth. Changing the constraints doesn’t do anything. It does not do anything. Having a pass on the layout and layout changing center and bounds, this is what does something. Let’s do exactly this. My constraint here is updated, and here from the animations block I’m just going to say, view.layoutIfNeeded(), which will force Auto Layout to immediately consider all constraints, and if anything changed, then recalculate everything and change the position and size of views. That’s why it’s called layoutIfNeeded(). Because maybe you didn’t change anything, then it will not do anything. This time, when I click it, then this change in the constraint is already animated. What is really important here to note is that I don’t animate anything on the table view. I have only one line that changes the constraint on the top bar. I don’t really tell the table view to do anything, but… since the top view and the table view are pinned together, changing the height of the one also changes the height of the other. I don’t really do this explicitly, I leave Auto Layout to solve all the rules that I laid down. And since the rules that I did was so that they stay together, that changes both. Also the changes to both are animated because their bounds were changed from within the animation block. This is really easy and now you can do all kinds of other stuff further from here.

The second thing I want to show is changing the multiplier on a constraint. The multiplier is read-only. To change a multiplier, you must loop over all the constraints, find the one you want to change, kill it, and then put a new one in its place.

for c in titleLabel.superview!.constraints {
  print(c)

  if c.identifier == "Center" {
    c.active = false

    let nc = NSLayoutConstraint(
      item: titleLabel,
      attribute: .CenterX,
      relatedBy: .Equal,
      toItem: titleLabel.superview!,
      attribute: .CenterX,
      multiplier: 1.5,
      constant: 0)
    nc.active = true
  }

}

In Xcode 7, there is a new field when you are editing your constraints called Identifier. Identifier is a way to give your constraint a name. This property also existed in iOS 8, but Xcode 6 did not have a text box for it. Everything I say is valid if you’re using Xcode 6, but you will need to use the user definer and time attributes to set identifier. In Cocoa, you have to set delegates and build relationships and things like this, but not with this API. With this API, as long as you set active property on your constraint to false, next time Auto Layout does a pass, it will say, “This constraint is not active, I don’t need it.” The same thing goes for when you are adding new constraints. When you set active, the next time Auto Layout does a pass, it will say, “This is active” and will add it to the list of constraints. There is an API that is a static method on NSLayoutConstraint where you can add more constraints at the same time, but what it does is go over all of them and set active to true. And those are the two easy ways to animate constraints. Make an outlet and change a constant and change a multiplier by removing a constraint and adding a new one. And most importantly, call layoutIfNeeded() from within an animation block if needed.

Stack Views (38:12)

Stack views are “Auto Layout without constraints.” This is a way to create Auto Layout animations without having to animate constraints. I have a third sample project to demonstrate stack views. Its premise is a shop application. It will show you the list of all books that are worked on that you can purchase also. The app consists of a list of books and when you click on the detail view controller, you will see the cover of the book with additional details.

import UIKit

class DetailViewController: UIViewController {

  var book: MasterViewController.Book!

  @IBOutlet weak var cover: UIImageView!
  @IBOutlet weak var name: UILabel!
  @IBOutlet weak var year: UILabel!

  @IBOutlet weak var topStack: UIStackView!

  override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    configureView()

    view.addGestureRecognizer(
      UITapGestureRecognizer(target: self, action: "didTap")
    )
  }

  func didTap () {
    UIView.animateWithDuration(0.4, animations: {
      self.topStack.arrangedSubviews.last!.hidden = !self.topStack.arrangedSubviews.last!.hidden
      })

  }

  func configureView() {
    //set the views
    if cover != nil {
      cover.image = UIImage(named: "\(book.imageName).jpg")
    }
    if name != nil {
      name.text = book.title
    }
    if year != nil {
      year.text = book.year
    }
  }
}

What I want to have is some text that will show the name of the book, year of release, etc. I’m going to call this Title using one label. Then, I’m going to call another one Book Title. Stack views are a way to group views and we have some views we want to group together forever. There’s a property called axis, which defines whether the stack view is horizontal or vertical. And there’s an alignment property that you can change from top, bottom or center. The stack view only keeps reference to the views that are arranged in there, and will take all the properties into consideration. All it does is manage the constraints for the arranged views so you don’t have to change the constraint of those views. It will create the constraints for you so that they’re aligned the way you want them.


Marin Todorov

Marin Todorov

Marin Todorov is an independent iOS consultant and publisher. He’s co-author on the book "RxSwift: Reactive programming with Swift" the author of “iOS Animations by Tutorials”. He's part of Realm and raywenderlich.com. Besides crafting code, Marin also enjoys blogging, writing books, teaching, and speaking. He sometimes open sources his code. He walked the way to Santiago.