Auto layout is awesome. It helps you create iOS interfaces that were not previously possible with springs and struts. However, becoming proficient at auto layout can require that you internalize some new concepts. In this post, I will attempt to cover those new ideas as concisely as I can.
What Auto Layout Is
Auto layout is powered by a mathematical process called linear programming. This approach is particularly good at simultaneously solving for lots of variables whose values are constrained. We will soon see how this process maps naturally to the problem of laying out a user interface. But first, we can derive a better understanding of auto layout by making some general observations.
One of the few counter-intuitive aspects of linear programming is that it generates solutions for all variables simultaneously. As programmers, we are accustomed to algorithms that iterate over lists, performing one task at a time. You will have to resist the temptation to think of auto layout in this way. Solutions are ‘all or nothing’, meaning that all variables will either get a satisfactory value, or the entire process will fail.
There are two ways that one can cause such a failure: over-constraining or allowing ambiguity:
- Over-constraining occurs when constraints conflict with each other, and it becomes impossible to find a valid solution for all variables.
- Allowing ambiguity is the opposite. In this type of error, the constraints do not supply enough information to solve for all variables.
Applying These Ideas to the UIView Hierarchy
How do we take the abstract idea of ‘solving for all variables’ and apply it to our UIView objects? For our purposes, think of ‘all variables’ as ‘the position and size of every view’. Or phrased another way, we’re solving for the frame of every view.
There’s one more tough concept to grasp before moving on: while the position and size of every view must be defined by the constraints, some of the values can be defined implicitly. To explain this fully, let’s take a look at the following diagram of a view with one subview.
Here we see that the subview has an implicitly defined width. Our constraints are only explicitly describing the width of the parent, and spacing on the sides of the subview. However, that is enough contextual information for auto layout to solve for the width of the subview because only one possible value can satisfy the existing constraints.
Still with me so far? Let’s sum the previous few paragraphs up as: Every valid set of constraints must implicitly or explicitly define all positions and sizes of all views.
A Few Exceptions
Some views are capable of autonomously determining what their size should be. They can infer their size based on the size of their content. This concept is called intrinsic content size, and it allows you to omit explicit width and height constraints for these types of views. An example could be a UILabel that can infer its size based on font and length of text.
Everything in Alignment
The most fundamental concept contained in each constraint is the spatial alignment of two views. More specifically, we’re looking at the alignment of a reference point from one view, to another reference point from another view. An example of this might be a situation where we want an image with a caption just underneath.
Think of this one constraint as a relationship between a pair of reference points, one from each view:
- Image View, Bottom
- Label, Top
Given no other parameters, auto layout will take these two edges and align them. All constraints follow this pattern. In the auto layout APIs, the ‘reference point’ is called the ‘attribute‘ of the view. Each constraint can get a little more interesting when we play with different combinations of these attributes. Using attributes, we can tell which parts of each view should be aligned. If we wanted to horizontally center a view inside its parent view, we might have a constraint with the following settings:
- Superview, CenterX
- Subview, CenterX
We can get a pretty radical departure from this layout by changing just one of the attributes on this constraint. Let’s examine an attribute change to the subview.
- Superview, CenterX
- Subview, Right
As you might intuitively expect, the subview has moved so that the edge we specified is aligned with the center of the superview. Let’s go back and look at what would happen if we had set the same attribute value, but on the superview instead of the subview.
- Superview, Right
- Subview, CenterX
Again, we have achieved a big change in our layout with a subtle change in our constraint attributes.
Tweaking the Knobs
So far we’ve only looked at examples of constraints where our intention is simply, ‘line this up with that’. However, each constraint is capable of doing more than just aligning two things at exactly the same point. Constraints have two more useful features: constants and multipliers. The mathematical formula that auto layout uses to account for these extra numbers is as follows:
firstAttribute == multiplier * secondAttribute + constant
Examples of constants are everywhere. Any time you specify an explicit width or height, or an explicit amount of spacing between two views, you are using a constant. Constraints that effect a view’s aspect ratio are another good example of constants and multipliers. This type of constraint uses both width and height attributes on the same view and applies a multiplier. If we use a multiplier of 2, we will get a view that is twice as wide as it is tall.
But wait, there’s more! We can use both constant and multiplier features on the same constraint. Here we see an example of the same multiplier, but with an additional constant height added.
Resilience with Different Screen Sizes or Content Sizes
The overall goal of any layout system like auto layout is to have a user interface that remains functional (and hopefully aesthetically pleasing) no matter what the content, and no matter the size of the interface. One of the best tools auto layout gives you to achieve this goal is the relation feature. The potential types are:
When picking one of these types, we are deciding what range of numbers can satisfy a constraint. The number that is used in the comparison is the constraint’s constant. Text is a typical use case for this type of constraint because it can be hard to predict how long a string will be. In this example we see a label, which is constrained to be greater than or equal to 10 points away from the right edge of the screen. In this first image, there’s no problem at all. Our text easily fits on the screen.
When we need to display a longer string, our constraint kicks into action, keeping the right edge of the label 10 points away from the edge of the screen.
What is Your Top Priority?
Earlier in this post, I made a point of mentioning that constraints cannot conflict with each other, which was not entirely true. You can add conflicting constraints into a layout, as long as one of the conflicting constraints has a higher priority. When this happens, the lower-priority constraints will be ignored. This type of behavior can be common if you have an app where lots of views resize. Device rotation is a common example. Constraints often don’t conflict immediately, but start to conflict when the view gets squeezed or stretched.
All UIView objects have two properties that determine how they interact with your constraints: content hugging priority and content compression resistance priority. Values for these priorities and for priorities on any constraint can be any integer from 1 to 1000. Here we see the default values for a typical view.
‘Content Hugging’ describes the idea that a view’s size should be no bigger than is necessary to show all of the content in said view. If a view’s content hugging priority is greater than any of its other constraints, that is exactly how the view will be sized.
Similarly, ‘Content Compression Resistance’ is the idea that a view will retain its size in order to show the entirety of its content, even when other constraints require it to squeeze to a smaller size. You can tune these priorities for each view separately to get the exact behavior that you need whenever your interface gets squeezed or stretched.
A Word of Advice
Auto layout is a high-powered tool to add to your arsenal. But with great power comes great responsibility. Poorly thought out constraints can cause a big headache to anyone that has to pick through them and fix them. My rule of thumb is to always set up your constraints in the same way you would describe the position of a UIView to another human being. By this I mean that when you read the values constraint’s properties, it should sound like a coherent description of your interface.
With that I conclude this post. I hope you’ve found this overview useful. Happy constraining!