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
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
NSView you're working with. How that works becomes clear in a few minutes.
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
NSView, you probably understand what they are. These are the available layout anchors for
These properties correspond to an
NSLayoutAnchor subclass. For example, the
bottomAnchor property of a view is of type
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
UIView instance, and create a layout constraint by invoking a factory method,
constraint(equalTo:constant:). This method accepts two arguments:
- a constant of type
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
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.
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
// 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)
NSLayoutAnchor API makes this more readable and more concise. This is the equivalent using the
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
The next layout constraint centers the container view horizontally in its superview. This is what that looks like using the
// 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)
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
bottomLayoutGuide properties conform to the
UILayoutSupport protocol. Through this protocol, they expose three properties:
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.