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 typeNSLayoutYAxisAnchor
bottomAnchor
of typeNSLayoutYAxisAnchor
heightAnchor
of typeNSLayoutDimension
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.