Working With Auto Layout And Layout Anchors

Creating layout constraints in code is not pretty. It is not always intuitive and it is verbose, even if you decide to use Apple's Visual Format Language.

In iOS 9 and OS X El Capitan, Apple introduced the NSLayoutAnchor class to make working with layout constraints in code easier and more intuitive. I must admit I was a bit skeptical at first. Even the Visual Format Language makes it difficult to manage constraints, how would another API make it easier.

Fortunately, I was wrong. The NSLayoutAnchor class makes creating and managing constraints almost painless. In addition to the NSLayoutAnchor class, Apple added a number of properties to UIView and NSView. The result is pretty nice.

How It Works?

There are a few things you need to know about the NSLayoutAnchor class. First, you are not supposed to explicitly create instances of the NSLayoutAnchor class. Instead, you obtain a reference to a layout anchor from the UIView or NSView you are working with. How that works becomes clear in a few minutes.

Second, the NSLayoutAnchor class can be considered an abstract class. In practice, you interact with one of its subclasses:

  • NSLayoutDimension: This subclass is used to create layout constraints that describe the width and height of a view.
  • NSLayoutXAxisAnchor: This subclass is used to create horizontal layout constraints.
  • NSLayoutYAxisAnchor: This subclass is used to create vertical layout constraints.

Third, the existing APIs for programmatically creating layout constraints do not warn you if you are defining invalid layout constraints. The NSLayoutAnchor class adds type checking to the mix, warning you about potential problems at compile time. While this is not 100% waterproof, it certainly helps creating the right set of constraints and facilitates debugging Auto Layout issues.

Layout Anchors

But what is a layout anchor? If you take a look at the new properties of UIView and NSView, you probably understand what they are. These are the available layout anchors for UIView:

  • widthAnchor
  • heightAnchor
  • topAnchor
  • bottomAnchor
  • leadingAnchor
  • trailingAnchor
  • leftAnchor
  • rightAnchor
  • centerXAnchor
  • centerYAnchor
  • firstBaselineAnchor
  • lastBaselineAnchor

These properties correspond to an NSLayoutAnchor subclass. For example, the bottomAnchor property of a view is of type NSLayoutYAxisAnchor.

But how do you create a layout constraint using a layout anchor? Creating layout constraints with layout anchors is almost identical to creating layout constraints in Interface Builder. Take a look at the following example.

// Create & Add Layout Constraint
let leadingConstraint = subview.leadingAnchor.constraintEqualToAnchor(superview.leadingAnchor, constant: 8.0)

// Activate Layout Constraint
leadingConstraint.active = true

We access the leadingAnchor property of view, a UIView instance, and create a layout constraint by invoking a factory method, constraintEqualToAnchor(_:constant:). This method accepts two arguments:

  • a NSLayoutAnchor instance
  • a constant of type CGFloat

Because leadingAnchor is of type NSLayoutXAxisAnchor, we need to pass in a NSLayoutXAxisAnchor instance as the first argument of constraintEqualToAnchor(_:constant:). The resulting layout constraint is automatically added to the view instance, but note that we need to activate it by setting its active property to true.

I am sure you agree that the above example is easy to read and understand. Adding layout constraints using the NSLayoutAnchor API is clear and concise. It is great to see that Apple continues to invest in improving Auto Layout. This reaffirms that Auto Layout is the preferred way to create user interfaces for modern Cocoa applications.

Refactoring

I would like to end this tutorial by rewriting the layout constraints we added in the last lesson Auto Layout Fundamentals. Download the project we created from GitHub and open it in Xcode.

Open ViewController.swift and navigate to the implementation of setupContainerView(). The first layout constraint we add in this method uses the NSLayoutConstraint API.

// Width
let constraintWidth = NSLayoutConstraint(
    item: containerView,
    attribute: .Width,
    relatedBy: .LessThanOrEqual,
    toItem: nil,
    attribute: .NotAnAttribute,
    multiplier: 1.0,
    constant: 320.0
)

// Add Constraint
containerView.addConstraint(constraintWidth)

The NSLayoutAnchor API makes this more readable and more concise. This is the equivalent using the NSLayoutAnchor API:

containerView.widthAnchor.constraintLessThanOrEqualToConstant(320.0).active = true

We access the widthAnchor of the container view and use it to create a layout constraint that constrains the width of the container view to be less than or equal to 320.0 points. Because constraintLessThanOrEqualToConstant(_:) returns an NSLayoutConstraint instance, we can activate the layout constraint by setting its active property to true.

The next layout constraint centers the container view horizontally in its superview. This is what that looks like using the NSLayoutConstraint API:

// Center
let constraintCenter = NSLayoutConstraint(
    item: containerView,
    attribute: .CenterX,
    relatedBy: .Equal,
    toItem: view,
    attribute: .CenterX,
    multiplier: 1.0,
    constant: 0.0
)

// Add Constraint
view.addConstraint(constraintCenter)

Using NSLayoutAnchor, we can replace this with one line of code.

containerView.centerXAnchor.constraintEqualToAnchor(view.centerXAnchor).active = true

For the next two layout constraints, we access the view controller's top and bottom layout guides. The idea is similar, though. The topLayoutGuide and bottomLayoutGuide properties conform to the UILayoutSupport protocol. Through this protocol, they expose three properties:

  • topAnchor of type NSLayoutYAxisAnchor
  • bottomAnchor of type NSLayoutYAxisAnchor
  • heightAnchor of type NSLayoutDimension
containerView.topAnchor.constraintEqualToAnchor(topLayoutGuide.bottomAnchor, constant: 8.0).active = true
containerView.bottomAnchor.constraintEqualToAnchor(bottomLayoutGuide.topAnchor, constant: 8.0).active = true

We need to modify the priority of the layout constraints for the last two layout constraints. This is not a problem as you can see below.

let constraintLeading = containerView.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor, constant: 0.0)
constraintLeading.priority = 750.0
constraintLeading.active = true

let constraintTrailing = containerView.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor, constant: 0.0)
constraintTrailing.priority = 750.0
constraintTrailing.active = true

Layout Anchors Rock

I don't know about you, but I am pleasantly surprised with the addition of the NSLayoutAnchor API. As I mentioned, it is great to see that Apple continues to invest in Auto Layout with the addition of stack views and layout anchors. In the past, I tried to avoid adding constraints in code because it is verbose and often confusing. With the addition of layout anchors, that has changed.

Questions? Leave them in the comments below or reach out to me on Twitter. You can download the source files of the tutorial from GitHub.

Stop Writing Swift That Sucks

Download the Swift Patterns I Swear By

About Bart Jacobs

About bart jacobs

My name is Bart Jacobs and I run a mobile development company, Code Foundry. I've been programming for more than fifteen years, focusing on Cocoa development soon after the introduction of the iPhone in 2007.

Stop Writing Swift That Sucks

In my free book, you learn the four patterns I use in every Swift project I work on. You learn how easy it is to integrate these patterns in any Swift project.