Looking for some quick fix examples for Auto Layout? Look no further!
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.
SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's all new Paywall Editor allow you to remotely configure your paywall view without any code changes or app updates.
Sponsor Hacking with Swift and reach the world's largest Swift community!
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.
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))
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, constant: 30)
centerY.priority = UILayoutPriority(999)
NSLayoutConstraint.activate([
centerY,
spacing
])
All views have two Auto Layout priorities that decide how much space they need:
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)]"
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:
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()
}
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!
SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's all new Paywall Editor allow you to remotely configure your paywall view without any code changes or app updates.
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.