Working With Auto Layout and Layout Anchors

Working With Auto Layout And Layout Anchors

working-with-auto-layout-and-layout-anchors

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

A few years ago, Apple introduced the NSLayoutAnchor class to make working with layout constraints in code easier and more intuitive. I must admit that I was a bit skeptical at first. Even the Visual Format Language makes it difficult to manage constraints. How would another API make that 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 Does It Work?

There are a few things you need to know about the NSLayoutAnchor class. First, you're 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're 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 don't warn you if you're defining invalid layout constraints. The NSLayoutAnchor class leverages type checking to warn you about potential problems at compile time. While this isn't completely bulletproof, it certainly helps creating the right set of constraints and facilitates debugging Auto Layout issues.

What Is a Layout Anchor?

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.

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 and Add Layout Constraint
let leadingConstraint = subview.leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: 8.0)

// Activate Layout Constraint
leadingConstraint.isActive = true

We access the leadingAnchor property of view, a UIView instance, and create a layout constraint by invoking a factory method, constraint(equalTo: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 constraint(equalTo:constant:). The resulting layout constraint is automatically added to the view instance, but note that we need to activate it by setting its isActive property to true.

I'm 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's great to see that Apple continues to invest in Auto Layout. It shows that Auto Layout is the preferred way to create user interfaces for modern Cocoa applications.

Refactoring

I'd 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.constraint(lessThanOrEqualToConstant: 320.0).isActive = 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 constraint(lessThanOrEqualToConstant:) returns an NSLayoutConstraint instance, we can activate the layout constraint by setting its isActive 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.constraint(equalTo: view.centerXAnchor).isActive = 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.constraint(equalTo: topLayoutGuide.bottomAnchor, constant: 8.0).isActive = true
containerView.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor, constant: 8.0).isActive = true

We need to modify the priority of the last two layout constraints. This isn't a problem as you can see below.

let constraintLeading = containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0)
constraintLeading.priority = UILayoutPriority(rawValue: 750.0)
constraintLeading.isActive = true

let constraintTrailing = containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0)
constraintTrailing.priority = UILayoutPriority(rawValue: 750.0)
constraintTrailing.isActive = true

Layout Anchors Rock

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