How Auto Layout Works: A Conceptual Guide

Alan Wang
Compass True North
Published in
11 min readMay 15, 2019

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 solves for these four variables.

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.

Source: Auto Layout Guide

Thus, creating a set of constraints means creating a system of equations.

Constraints are translated into a system of equations.

Auto Layout takes this system of equations and solves it for the layout variables needed for each view.

Auto Layout solves the system of equations for the values of the layout variables.

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.

You need the right number of equations vs. unknowns in order to have a single solution.

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.

If there are too few constraints, multiple layouts are valid. Auto Layout arbitrarily 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.

If Auto Layout cannot satisfy all constraints, it will break them in order of priority. In the above example, if the superview width is 200, and the view is 50 from each side, then the view’s width must be 100. This conflicts with the constraint that says the view’s width is 300. Since the 300 constraint is lower priority, it is broken so that Auto Layout can find a solution.

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).

A broken optional constraint can be used to influence the final layout. In the above example, the width of the view can be anywhere between 0 to 100 given the top set of constraints. The addition of an unsatisfiable, low priority constraint pushes the view to be as wide as possible in order to minimize the error. (Assume all other priorities are 1000.)

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.

If translatesAutoresizingMakeIntoConstraints is true, the view’s frame property will be turned into 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).

Intrinsic content size is translated into constraints using content hugging and compression resistance priorities.

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.

Auto Layout does not create constraints if there is noIntrinsicMetric.

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 Views
UIStackView 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.

Auto Layout automatically adds constraints for stack views

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.
Scroll view anatomy: The scroll view consists of a view port that serves as a window into the content view. Scrolling the scroll view moves the view port around, showing different parts of the content view.

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, and center constraints between the scroll view and its subviews are constrained to the frame (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 have layoutSubviews() 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 pass
  • layoutSubviews 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.

The update constraints pass. The view hierarchy is traversed from leaf to root. Views marked as needing constraint update have updateConstraints called on them. This may result in more constraint change events, resulting in more views being marked as needing layout.

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 and setCenter on the subview with the appropriate values.

In this manner, the size and position of views in the view hierarchy are updated as needed.

The layout subviews pass. View hierarchy is traversed from root to leaf. Views marked as needing layout have layoutSubviews called on them. The views pull the layout variables from the layout engine and call setBounds and setCenter on their subviews.

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 by setNeedsUpdateConstraints.
  • Then, it calls layoutSubviews for every view that has been marked with setNeedsLayoutSubviews.
  • 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.

--

--