When Auto Layout works it’s magical. When it doesn’t… well, let’s just say you might want one of these.
If you’re tired of fighting with Auto Layout, you’re not alone. Even though it’s a remarkable piece of software, Auto Layout can at times be baffling, overly complicated, and perhaps even downright obtuse.
Don’t worry, though, because help is at hand: there are lots of wrappers around Auto Layout that can help to make your layout code easier or more expressive. So, in this article I’ve picked out some you might want to consider and given examples of each so you can compare for yourself.
Note: Because each of these is more or less the same thing – a domain-specific language for Auto Layout - I’m going to get right to the code examples, because that’s what makes them stand out from each other.
GO FURTHER, FASTER Unleash your full potential as a Swift developer with the all-new Swift Career Accelerator: the most comprehensive, career-transforming learning resource ever created for iOS development. Whether you’re just starting out, looking to land your first job, or aiming to become a lead developer, this program offers everything you need to level up – from mastering Swift’s latest features to conquering interview questions and building robust portfolios.
Cartography uses a handful of global functions to let you constrain, align, and distribute views using closures. For example, you set the width and height of a view like this:
constrain(view1) {
$0.width == 100
$0.height == 100
}
That constrain()
function comes with alternatives that handle up to five views at a time, so if you wanted to configure multiple views with the same size you could write this:
constrain(view1, view2, view3) {
$0.width == 100
$0.height == 100
$1.width == 100
$1.height == 100
$2.width == 100
$2.height == 100
}
For times you need more than five you can pass an array instead, and when you do so you’ll be given an array of things to configure inside your closure, like this:
constrain([view1, view2, view3]) {
$0.forEach {
$0.width == 100
$0.height == 100
}
}
Cartography uses the operator ~
to enable layout priorities, for example $0.width == 100 ~ 500
.
Alongside constrain()
there are also the align()
and distribute()
functions to align views and space them evenly. For example, we could use constrain()
and align()
to align two views so they have the same leading edge:
constrain(view1, view2) {
align(leading: $0, $1)
}
Using distribute()
you can ask for views to be spaced out horizontally or vertically, using a fixed amount of spacing between each of them:
constrain(view1, view2, view3) {
distribute(by: 10, horizontally: $0, $1, $2)
}
EasyPeasy uses one operator, <-
, a collection of custom data types, plus some method swizzling and extensions to produce some syntax that works surprisingly well once you’re over the initial shock.
Mimicking the example from Cartography above, we can set the width and height of a view like this:
view1 <- [
Width(100),
Height(100)
]
That can be expressed more precisely using Size
, which combines both Width
and Height
, like this:
view1 <- [
Size(100)
]
For handling priorities, EasyPeasy groups them into low (equivalent to priority 0 in Auto Layout), medium (500), high (750), required (1000), and custom, where the last one lets you specify a precise Auto Layout number of your choosing.
You can then attach these to individual constraints like this:
view1 <- [
Width(100).with(.high),
Height(100)
]
Alternatively, you can attach them to the whole array of constraints, like this:
view1 <- [
Width(100),
Height(100)
].with(.high)
Arguably the most powerful feature of EasyPeasy is its ability to store a variety of layouts depending on layout conditions – you literally give it all the layouts you might want, and let it select which is correct.
For example, you might want a view to be 100 points in size and centered when using the horizontal regular size class, but pinned to the left and right edges in the compact size class. Here’s that using EasyPeasy:
view1 <- [
Size(100),
Center()
].when { $0.isHorizontalRegular }
view1 <- [
Height(100),
Leading(0),
Trailing(0)
].when { $0.isHorizontalCompact }
Before I’m finished I want to show you one highlight:
view1 <- Edges(10)
That one tiny, concise line of code is enough to make view1
fill its superview with 10 points of spacing on all sides.
SnapKit adds a snp
property to your views that can be used to add, remove, and adjust constraints quickly and easily. Continuing our example, we can make a view that is precisely 100 points in size like this:
view1.snp.makeConstraints {
$0.width.equalTo(100)
$0.height.equalTo(100)
}
You can put that on one line if you want, either like this:
view1.snp.makeConstraints {
$0.size.equalTo(100)
}
Or even this:
view1.snp.makeConstraints {
$0.width.height.equalTo(100)
}
Helpfully, what you pass into equalTo()
can be a number or a different view. So, we could ask view1
to snap to the edges of its our main view like this:
box.snp.makeConstraints {
$0.edges.equalTo(view)
}
You could even add some edge insets if you wanted:
box.snp.makeConstraints {
$0.edges.equalTo(view).inset(10)
}
That doesn’t have the pithy conciseness of EasyPeasy, but I’d say it was more readable at first glance.
This method of chaining extends to priorities as well, and you can end any of your constraints with a .priority()
call either using one of the built-in presets:
$0.edges.equalTo(view).inset(10).priority(.medium)
Or using a free-form number:
$0.edges.equalTo(view).inset(10).priority(556)
Stevia is probably the most flexible of all the options here, offering three different ways of laying out your views – one of which is a visual formatting language that lets you configure both the X and Y layout of your views in a single pass.
Let’s start simple: before you do anything you need to call view.sv()
on your views, which is a shortcut to disable the automatic translation of autoresizing masks and adding a subview to another view. So, you’d write something like this:
view.sv(view1)
Once that’s done you can set the width and height of view1
like this:
view1.width(100)
view1.height(100)
As with some of the other options here, those calls are chainable:
view1.width(100).height(100)
They can also be wrapped in a single call:
view1.size(100)
What gives Stevia an advantage here is that these sizing methods have awesome percentage-based alternatives that make constraints really natural. For example, we could tell view1
that it should be half the size of its parent and centered in just two lines of code:
view1.size(50%)
view1.centerInContainer()
You have to admit, that’s remarkably concise!
You can make a single view fill all available space in its parent using its fillContainer()
method, which has an optional padding parameter. For example, we could tell view1
to fill the screen with 10 points of spacing around all edges like this:
view1.fillContainer(10)
There’s also a neat bonus method called heightEqualsWidth()
that adds an aspect ratio constraint.
And now for something that really sets Stevia apart: it has a visual layout language that lets you create both horizontal and vertical constraints in a single pass.
VFL is a real love/hate topic in Auto Layout because it can start to look like line noise after a while, and Stevia’s equivalent is no different. This, for example, will lay out our screen so that view1
has 50 points of spacing from the left and right edges, 100 points of spacing from the top, and is 200 points high:
view.layout (
100,
|-50-view1-50-| ~ 200
)
While I’m not a big fan of VFL, it’s great to see Stevia providing the option, and it’s even better to see they’ve made it significantly safer by removing the usage of strings.
Although it’s a shame there’s no Stevia API for priorities (all constraints are created as priority 751), it does deserve bonus praise for its comprehensive documentation.
GitHub link (no documentation)
As the name suggests, Tiny Constraints is a relatively simple Auto Layout wrapper that makes a selection of tasks very quick. Just like other options here you can use it set width and height constraints:
view1.width(100)
view1.height(100)
You can make a view go edge to edge inside its parent like this:
view1.edges(to: view)
And there’s an option that allows you to specify padding on each side, but it uses UIEdgeInsets
so it’s a bit unwieldy:
view1.edges(to: view, insets: UIEdgeInsets(top: 10, left: 10, bottom: -10, right: -10))
Notice the negative insets on the bottom and right sides; that doesn’t feel terribly intuitive to me.
As for priorities, TinyConstraints gives you four: fittingSize
has a value of 50, low
has a value of 250, high
750, and required
1000. They are easy enough to use because you can add them to the other method calls above, like this:
view1.height(100, priority: .low)
view1.width(100, priority: .high)
There are a few bonus methods for positioning various anchors, for example view1.center(in: view)
will center the view, or view1.bottom(to: view)
to place it at the bottom of the screen, but that’s about it – tiny, like the name says.
I spent about an hour with each of the options on this list, trying to make simple layouts and trying to make more advanced layouts too.
I hope it’s pretty clear to you that each have their own advantages: Cartography probably has the fewest surprises, EasyPeasy is extraordinarily concise, SnapKit has extremely clear syntax that makes it easy to read, Stevia is ridiculously powerful, and TinyConstraints really lives up to its name – it’s tiny.
As well as each fitting different uses, it’s important to say that this is a deeply personal choice: some people love Perl-like code brevity, whereas others prefer more verbose method and parameter names that help make their code self-documenting. While writing this article I really fell in love with EasyPeasy – the syntax is quite alien at first, but it’s worth the work just to get the excellent size class functionality.
With those provisos out of the way, if I were to choose an Auto Layout wrapper today I would go for Stevia. Honestly, it wasn’t one I had used before I put together this list, but I was hugely impressed by its extraordinary flexibility: being able to switch between specific amounts, percentages, and relative layouts freely is quite liberating, and its selection of other helper methods provides a powerful arsenal to tackle even the gnarliest of layout needs.
I also felt that Stevia offers a sensible blend of concise code and readable code, which ultimately gave it the edge over EasyPeasy. Yes, EasyPeasy’s size class system is impressive, but its syntax is quite unlike anything seen in Apple’s own frameworks.
So, Stevia is a worthy winner of a Hacking with Swift Recommended award, and I encourage you to give it a try – it could well be the beginning of the end for your Auto Layout problems!
UIStackView
do what you need?If you don’t fancy learning a whole new framework just to make your layouts easier, keep in mind that ever since iOS 9 Apple has been recommending you start your layouts with a UIStackView
.
Stack views aren’t like regular views at all – you can’t give them a background color, for instance – and instead are solely there to help control your layouts. If you haven’t used them before, I made a video just for you…
When you have an issue with your Auto Layout constraints, Xcode spitting out a hundred lines of log messages to try to “help” is probably not quite as useful as Apple intended.
Fortunately, John Patrick Morgan has built a site that can help transform Xcode’s logs into simple, beautiful visual representations: WTF Auto Layout, which (before you imagine otherwise!) stands for “Why The Failure?”
Give it a try next time you hit a constraints problem: https://www.wtfautolayout.com.
GO FURTHER, FASTER Unleash your full potential as a Swift developer with the all-new Swift Career Accelerator: the most comprehensive, career-transforming learning resource ever created for iOS development. Whether you’re just starting out, looking to land your first job, or aiming to become a lead developer, this program offers everything you need to level up – from mastering Swift’s latest features to conquering interview questions and building robust portfolios.
Link copied to your pasteboard.