How Auto Layout Works: A Conceptual Guide
If you’re reading this article you probably already know about Auto Layout, Apple’s layout engine. It’s great when it works, but mind-numbingly infuriating when it doesn’t. Debugging a bad set of constraints often results in increasingly arbitrary trial and error until either the view lays out as intended, or the dreaded “Unable to simultaneously satisfy constraints” error goes away. The result is often a messy, overly complicated, poorly understood set of constraints. This article aims to improve on that problem by providing you with a more intuitive understanding of Auto Layout.
Auto Layout has one job: Compute and set the frame for every view in the view hierarchy. (Technically it sets the alignmentRect
, but that’s more or less the same thing.)
This means that for every view in the view hierarchy, Auto Layout must determine four layout variables:
- The x-coordinate of the view’s origin
- The y-coordinate of the view’s origin
- The width of the view
- The height of the view
Auto Layout accomplishes this task by asking the developer to define a set of constraints, describing the desired layout of their views. Auto Layout takes these constraints, as well as others added by the system, and uses them to calculate and set the appropriate frames (alignment rects) during the “layout pass.”
In this article we will cover:
- Constraints — What they are, and how they’re used
- System Constraints — Additional constraints added by the system
- The Layout Pass — How Auto Layout takes your constraints and lays out your views
Note: We will not discuss how to implement constraints. There are a number of different ways to implement constraints, from third party libraries, to Interface Builder, to ASCII art. Resources for creating constraints are available on the internet.
What is a Constraint?
A constraint is a mathematical equation.
Thus, creating a set of constraints means creating a system of equations.
Auto Layout takes this system of equations and solves it for the layout variables needed for each view.
Ambiguous Layouts and Unsatisfiable Constraints
You may recall from math class that in order to solve a system of equations, you must have as many independent equations as unknown variables. If you don’t have enough equations, there are an infinite number of solutions. If you have too many equations, then there are no solutions.
The same concept applies to constraints. For each view, you need enough constraints such that, mathematically, there is one and only one possible solution.
Ambiguous Layout
If there are too few constraints, you will have an ambiguous layout. In this situation, there are an infinite number of possible solutions that satisfy the constraints. Auto Layout doesn’t know which one you want so it simply chooses one.
Unsatisfiable Constraints
On the other hand, if you have too many constraints, then those constraints will conflict, and Auto Layout will find that it is impossible to satisfy all of the constraints. In order to provide a layout, Auto Layout will break as many constraints as necessary to find a solution. The order in which constraints are broken depends on their priority. If there is a tie, Auto Layout will arbitrarily select a constraint to break.
Constraint Priority
Every constraint has a priority from 1 to 1000, which you can specify. Apple provides a few predefined constants to help you out:
.required
= 1000.defaultHigh
= 750.defaultLow
= 250.fittingSizeLevel
= 50
Any constraint with a priority less than 1000 is considered optional, and will be broken if the system is over constrained. However, broken optional constraints can still influence the layout. The system will seek the solution that comes closest to satisfying the optional constraint. For instance, if the constraint a = b is optional and broken, then the system will use the solution that minimizes abs(a-b).
Pro-Tip: Apple suggests setting non-required priorities around the defaults, rather than equal to the defaults. The defaults are used internally by Apple, so you don’t want to come into conflict with them, as unexpected things may happen when internal stuff breaks.
The World of Constraints
In the world of Auto Layout, everything is translated into constraints. In addition to the constraints you create yourself, Auto Layout may create some additional constraints depending on the view.
Pro-Tip: If you want to see exactly what constraints are installed on your views, you can use the constraintsAffectingLayoutForAxis:
method. I recommend calling this method from inside layoutSubviews
, as you are guaranteed that all relevant constraints will be in place by then (see the Auto Layout in Action section below).
Frames and the AutoResizingMask
In ye olden times, frames were set directly by the developer. This practice was problematic because the resultant layouts were not adaptable to change. In order to relieve some of this pain, Autoresizing Masks were introduced.
Apple has since moved away from these dark ages and embraced Auto Layout. However, backwards compatibility is maintained by translating these frames and masks into constraints. You can still set the frame of a view directly if translatesAutoresizingMaskIntoConstraints
is set to true
on that view. Auto Layout will take this frame and translate it into a set of constraints.
translatesAutoresizingMaskIntoConstraints
defaults to true
. As a result, this is probably the number one cause of accidentally over constrained layouts. If you are defining a view’s position and size with your own constraints, be sure to set this property to false
.
Pro-Tip: If translatesAutoresizingMaskIntoConstraints
is false
, avoid setting the frame of that view. Whatever you set can be overwritten at any time by Auto Layout, and can also interfere with Auto Layout.
Intrinsic Content Size, Content Hugging, and Compression Resistance
A common desire with Auto Layout is to create self-sizing views — views that can determine their own size, based solely on their content.
In general, most views can determine their “self-size” using constraints to their subviews. However, if there are no subviews the view will need to determine its size via some other method. For these situations, UIView
exposes the intrinsicContentSize
method. A view overrides its intrinsicContentSize
method to communicate its desired size to Auto Layout. For example, a UILabel
computes its size based on rendered text measurements, and communicates its desired size via intrinsicContentSize
.
How closely a view adheres to its intrinsicContentSize
depends on two content priorities:
- Content hugging — A higher priority makes it harder for the view to be larger than its intrinsic size. By default, this is set to
.defaultLow
. - Compression resistance — A higher priority makes it harder for the view to be smaller than its intrinsic size. By default, this is set to
.defaultHigh
.
If the view has an intrinsicContentSize
, Auto Layout takes the intrinsicContentSize
, content hugging, and compression resistance priorities, and turns them into a set of 4 optional constraints (2 for each axis).
Most views do not override intrinsicContentSize
, and will return (-1, -1)
if you try to access their intrinsicContentSize
. This -1
corresponds to the noIntrinsicMetric
constant that Apple uses to denote that no intrinsic size is specified. If there is no intrinsic width or height, Auto Layout will not create the corresponding constraints.
In Apple’s own words (from UIView.h
):
Note that not all views have an intrinsicContentSize. UIView’s default implementation is to return (UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric). The _intrinsic_ content size is concerned only with data that is in the view itself, not in other views. Remember that you can also set constant width or height constraints on any view, and you don’t need to override instrinsicContentSize if these dimensions won’t be changing with changing view content.
Pro-Tip: Never set these content priorities to .required
. Doing so will likely result in unsatisfiable constraints.
Pro-Tip: Avoid giving your subviews the same content priority as each other. For example if you have two subviews, avoid setting both of their contentHuggingPriority
to 750. Doing so will likely result in ambiguous layouts.
Pro-Tip: The only reason you should need to override intrinsicContentSize
is if your custom view’s size must be determined from non-view content. Apple has repeatedly advised against overriding it for any other reason.
Special Cases
Here we describe some views for which it may be unclear how they interact with Auto Layout.
Stack ViewsUIStackView
was introduced to simplify the usage of Auto Layout. With it, you can add a number of views as arranged subviews and UIStackView
will automatically create and manage a set of constraints based on its spacing
, distribution
, and alignment
properties.
If you’re interested in knowing exactly what constraints Apple installs for UIStackView
, check out my followup article about UIStackView constraints.
Pro-Tip: Be careful when constraining toUIStackView
or its subviews. Keep in mind that UIStackView
will create additional constraints. Be careful not to accidentally over constrain it, or its subviews.
Scroll Views
Scroll views (such as those in UIScrollView
, UITableView
, and UICollectionView
) require special treatment because it’s not obvious how they should be sized. Scroll views can be thought of as having two parts:
- Content view — This is where the content you want to display goes.
- View port — This is the “window” through which you see the content. You can scroll around the content to see other parts of it through this “window.” This is represented by the scroll view’s
frame
.
Adding a subview to a scroll view actually adds it to the scroll view’s “content view”. As a result, constraints on these subviews would be between the subview and the “content view”, rather than the subview and the “view port”. The “view port” has no idea how big it should be, since it has no constraints to the scroll view’s subviews.
In order to deal with this problem, Apple changes the behavior of constraints when constraining to scroll views. Per Apple’s documentation:
- Constraints between the scroll view and views outside of the scroll view are constrained to the
frame
(i.e. the “view port”). - Edge constraints (i.e.
top
,bottom
,leading
,trailing
) between the scroll view and its subviews (i.e. the views in its “content view”) are constrained to the “content view.” width
,height
, andcenter
constraints between the scroll view and its subviews are constrained to theframe
(i.e. the “view port”).
Visualizing Constraints
When thinking about constraints, I find it helpful to visualize the view and think about which parts of the view are rigid, and which parts are flexible. For the flexible portions, imagine them growing and shrinking. What parts of the view will move with them? How far can they go before breaking a constraint? Which constraints will be broken and in what order?
Auto Layout In Action
So what actually happens when you add and remove constraints? How do your constraints get translated into a layout on screen? In the following section we walk through the layout process step by step.
Constraint Change Events
There are a number of events that may cause your constraints to change, the most obvious of which is activating and deactivating them in your code. Other events may include modifying a constraint’s constant
or priority
values, or adding and removing views from the view hierarchy. With every constraint change event, the following occurs:
- Auto Layout adds, modifies, or removes equations from its system of equations.
- Auto Layout solves the updated equations for new values of layout variables.
- For views whose layout variables have changed, Auto Layout notifies the view of such.
- In response to the notification, the view calls
superview.setNeedsLayout()
. This marks the superview as needing to havelayoutSubviews()
called whenever the next layout pass happens.
In this manner, constraint changes mark views for layout updates.
Layout Pass(es)
Layout updates are executed by the deferred layout pass. Layout passes occur at the device screen’s refresh rate, meaning they can happen as often as 120 times a second. The purpose of the deferred layout pass is to update the positioning of views. Each deferred layout pass actually consists of two passes through the view hierarchy:
updateConstraints
passlayoutSubviews
pass
Update Constraints Pass
First, the system traverses your view hierarchy from leaf to root, calling updateConstraints
on any view that has been explicitly marked with setNeedsUpdateConstraints
. Depending on the view’s implementation of updateConstraints
, this may result in more constraint change events, resulting in more views marked for layout. This pass is your view’s last chance to change its constraints before the imminent layoutSubviews
pass.
Pro-tip: There is generally no reason to override updateConstraints
, or call setNeedsUpdateConstraints
. It is often cleaner to simply change the constraints immediately, rather than defer those changes with setNeedsUpdateConstraints
. The only benefit of changing constraints in updateConstraints
is that it is slightly more performant. In updateConstraints
, constraint changes are batched into a single constraint change event. However, the same benefit can be achieved simply by using NSLayoutConstraint.activate
and deactivate
methods.
Layout Subviews Pass
Finally, the system traverses your view hierarchy again, this time from root to leaf. It calls layoutSubviews
on every view that has been previously marked with setNeedsLayout
. In layoutSubviews
, the following occurs. For each subview:
- The view retrieves the subview’s layout variables from the layout engine.
- The view calls
setBounds
andsetCenter
on the subview with the appropriate values.
In this manner, the size and position of views in the view hierarchy are updated as needed.
Pro-tip: Overriding updateConstraints
or layoutSubviews
can be risky, since these methods are called so often (potentially 120 times/second).
Drawing
Finally, these changes are drawn to the screen.
Closing
And that’s it! In summary:
- Constraints are simply mathematical equations that describe the layout variables of views.
- These equations are collected into a system of equations by the Auto Layout engine.
- Whenever this system of equations changes, the engine solves the updated system for new values of layout variables.
- If a view’s layout variables change, that view calls
superview.setNeedsLayout
. - Independently, the deferred layout pass runs at the device screen’s refresh rate.
- For each run, it calls
updateConstraints
on any views that have been marked bysetNeedsUpdateConstraints
. - Then, it calls
layoutSubviews
for every view that has been marked withsetNeedsLayoutSubviews
. layoutSubviews
updates the size and position of every subview within the view.- The new layout is drawn to screen.
Hopefully this article has given you a better understanding of Auto Layout and how it works.