FREE: Read a new Swift article every day – click here! >>

The Auto Layout cheat sheet

Paul Hudson       @twostraws

Auto Layout is a powerful tool for creating flexible, maintainable rules for your user interface. It can also be a real brain vortex if you’re unlucky – it’s something that makes hard things easy and easy things hard.

To help relieve the pain, in this article I’ve put together code snippets to solve a variety of common Auto Layout problems: creating constraints, animating constraints, adjusting constraints at runtime, and more.

 

  • Advent of Swiftmas Day Five: Today’s Swiftmas surprise comes from Itty Bitty Apps, who are offering 25% off their user interface debugging tool, Reveal. This remarkable app is like having an X-ray for your UI – visit their site here and use the code “SWIFTMAS” to activate your discount!

     

Anchors

Auto Layout anchors are by far the easiest way to make constraints, and they also happen to have a really natural form too. In the code below I’ve used childView and parentView as names of example views, where the child is placed inside the parent.

 

Pin a child view to edges of its parent

This will make the child view run to the very edges of its parent:

NSLayoutConstraint.activate([
    childView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor),
    childView.trailingAnchor.constraint(equalTo: parentView.trailingAnchor),
    childView.topAnchor.constraint(equalTo: parentView.topAnchor),
    childView.bottomAnchor.constraint(equalTo: parentView.bottomAnchor)
])

You should mostly use leading and trailing anchors rather than left and right anchors, because leading and trailing automatically flip when your app is running in a right-to-left locale.

Tip: When activating or deactivating lots of constraints at once, it’s more efficient to pass them as an array to activate() and deactivate() respectively.

 

Pin a child view to the safe area guides of its parent

This will make a child view run to the edges of the safe area layout guides of its parent, which means it won’t go near rounded corners or the home indicator:

NSLayoutConstraint.activate([
    child.leadingAnchor.constraint(equalTo: parent.safeAreaLayoutGuide.leadingAnchor),
    child.trailingAnchor.constraint(equalTo: parent.safeAreaLayoutGuide.trailingAnchor),
    child.topAnchor.constraint(equalTo: parent.safeAreaLayoutGuide.topAnchor),
    child.bottomAnchor.constraint(equalTo: parent.safeAreaLayoutGuide.bottomAnchor)
])

 

Make a child view match the size of its parent

// give the child the same width as its parent
child.widthAnchor.constraint(equalTo: parent.widthAnchor).isActive = true

// give the child the same height as its parent
child.heightAnchor.constraint(equalTo: parent.heightAnchor).isActive = true    

// give the child half the width as its parent
child.widthAnchor.constraint(equalTo: parent.widthAnchor, multiplier: 0.5).isActive = true

// give the child the same width as its parent, minus a little padding on the edges
child.widthAnchor.constraint(equalTo: parent.widthAnchor, constant: -40).isActive = true

Tip: If you’re using storyboards to make your Auto Layout constraints, you can also use ratios as your multiplier for things like width and height. For example, using 1:2 will make the child view half the width of its parent.

 

Make a child stay a certain distance away from the edges of its parent

We’ve been using equality constraints above, but here’s an example using greater than or equal to:

NSLayoutConstraint.activate([
    child.leadingAnchor.constraint(greaterThanOrEqualTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20),
    child.trailingAnchor.constraint(greaterThanOrEqualTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 20)
])

There’s also lessThanOrEqualTo for the opposite effect.

 

Make a child have a natural width in its parent

Auto Layout gives us two useful layout guides: one that comes with the suggested system margin, and one that ensures a readable width for text.

// give the child the system-recommended margins on the leading and trailing edges
NSLayoutConstraint.activate([
    child.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
    child.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor)
])

// make sure the child is the width Apple recommends for comfortable reading
NSLayoutConstraint.activate([
    child.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor),
    child.trailingAnchor.constraint(equalTo: view.readableContentGuide.trailingAnchor)
])

 

Make a child view have a precise size

// make the child exactly 200 points wide
child.widthAnchor.constraint(equalToConstant: 200).isActive = true

// make the child exactly 200 points high
child.heightAnchor.constraint(equalToConstant: 200).isActive = true

Note: Be careful when using absolute sizes, because it can cause problems with labels if their text overflows.

Creating constraints using Visual Format Language

Although it’s not something you’ll see often, Visual Format Language (VFL) can help you get started making constraints because it has a relatively simple API for basic layouts.

 

Make a child view stretch the full width and height of its parent

let viewsDictionary = ["child": child]
parent.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[child]|", metrics: nil, views: viewsDictionary))
parent.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[child]|", metrics: nil, views: viewsDictionary))

 

Make a child view stretch the full width and height of its parent with some margins

// use system-standard margins
parent.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[child]-|", metrics: nil, views: viewsDictionary))
parent.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[child]-|", metrics: nil, views: viewsDictionary))

// use margins of exactly 50 points
parent.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-50-[child]-50-|", metrics: nil, views: viewsDictionary))
parent.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-50-[child]-50-|", metrics: nil, views: viewsDictionary))

 

Pin a child view to one edge of its parent

If you remove one of the pipe symbols in your VFL string, it means your child view will be pinned to the other edge. So, if you write H:[child]| rather than H:|[child]| it will place your child view at the right edge of its parent.

// pin to the left edge
parent.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[child]", metrics: nil, views: viewsDictionary))

// pin to the right edge
parent.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[child]|", metrics: nil, views: viewsDictionary))

 

Specifying a view’s size

You can set a specific size for a view by writing it in parentheses. For example:

// make the child view 100 points wide
parent.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[child(==100)]", metrics: nil, views: viewsDictionary))

// make the child view at least 100 points wide
parent.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[child(>=100)]", metrics: nil, views: viewsDictionary))

 

Sharing sizes across components

You can specify a dictionary of metrics, and use values from that dictionary in your VFL string. For example:

let metrics = ["childWidth": 88]
parent.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[child(childWidth)]", metrics: metrics, views: viewsDictionary))

Controlling constraint priorities

It’s common to attach multiple constraints for the same value, using priorities to decide at runtime which ought to be activated.

Constraints are created with priority 1000 by default, which means they are required: if Auto Layout’s solver can’t make it work, it will be removed and you’ll see a large error in your debug window. Priorities go downwards from 999 to 0, which is the lowest possible priority.

Auto Layout always tries to do its best to satisfy all active constraints as best as possible, which means it will first make sure all required constraints are matched, then try to get as close as it can for non-required constraints.

This code makes a title label centered vertically as much as possible, but always makes sure it’s at least 30 points below the image above it:

let centerY = title.centerYAnchor.constraint(equalTo: view.centerYAnchor)
let spacing = title.topAnchor.constraint(greaterThanOrEqualTo: image.bottomAnchor)
centerY.priority = UILayoutPriority(999)

NSLayoutConstraint.activate([
    centerY,
    spacing
])

All views have two Auto Layout priorities that decide how much space they need:

  • The Content Hugging Priority decides how closely the view will resist stretching outwards to fill more space. This defaults to 251.
  • The Content Compression Resistance priority decides how much the view will resist being made smaller than you asked when space is tight. This defaults to 750.

So, by default views expand more easily than they are squashed, which makes sense – if a button is a little wider than normal it’s not a problem, but if it gets made too small then it’s likely the title will get cut off.

If you’re using VFL, you can control the priority of each constraint by appending an @ sign then a number, like this:

"H:|[child(childWidth@999)]"

Adjusting constraints at runtime

It’s common to want to add, remove, and adjust constraints at runtime, for example if you want to show some text you might want to move an image down to make space above it.

To make this work you create three constraints:

  1. A required constraint pinning the image view to the top of its parent.
  2. A required constraint pinning a label to the top of its parent.
  3. A required constraint pinning the image view to the bottom of the label.

You then create properties for those constraints, so you can reference them in code.

The trick is to make sure you flip between using constraint 1 and using constraints 2 and 3. So, when the label is shown you write this:

imageToTopConstraint.isActive = false
labelToTopConstraint.isActive = true
imageToLabelConstraint.isActive = true

If you use non-required constraints - i.e., anything with a priority lower than 1000 – you can do even less work, because you can leave the non-required constraints active while just toggling the required one.

 

Animate changing Auto Layout constraints

Put your changes inside an animation block, then call layoutIfNeeded() like this:

UIView.animate(withDuration: 1.0) { [weak self] in
    self?.imageToTopConstraint.isActive = false
    self?.view.layoutIfNeeded()
}

Summary

You can do a lot in Auto Layout, as long as you’re able to find a way to clearly and completely specify constraints for the layout you want.

Auto Layout needs to know the exact size and position of every view at runtime otherwise you get ambiguous layouts, which is where the finished view layout varies randomly between app runs.

Broadly speaking you’ll want to prefer using anchors as much as possible, because they are the simplest and most commonly used approach to making constraints. VFL might look nice at first, but there are just so many things it can’t express that you’ll soon grow tired of it.

If you have any more Auto Layout tips and tricks, send them over to me on Twitter!

  • Don’t forget: you can save 25% on the Reveal app, which is the most powerful way to debug your user interfaces! Visit their site here and use the code “SWIFTMAS” to activate your discount!

 

MASTER SWIFT NOW
Buy Testing Swift Buy Practical iOS 12 Buy Pro Swift Buy Swift Design Patterns Buy Swift Coding Challenges Buy Server-Side Swift (Vapor Edition) Buy Server-Side Swift (Kitura Edition) Buy Hacking with macOS Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with Swift Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

About the author

Paul Hudson is the creator of Hacking with Swift, the most comprehensive series of Swift books in the world. He's also the editor of Swift Developer News, the maintainer of the Swift Knowledge Base, and Mario Kart world champion. OK, so that last part isn't true. If you're curious you can learn more here.

Was this page useful? Let me know!

Average rating: 5.0/5

Click here to visit the Hacking with Swift store >>