Pain Free Constraints with Layout Anchors

A frequent complaint with Auto Layout is how verbose and unreadable the syntax is for programmatically creating constraints. Fortunately iOS 9 has done a lot to improve things. Stack Views have removed the need for us to create many of the constraints in typical layouts. Overlooked by comparison, but just as useful, was the introduction of layout anchors and layout guides. From the Apple Auto Layout Guide:

You have three choices when it comes to programmatically creating constraints: You can use layout anchors, you can use the NSLayoutConstraint class, or you can use the Visual Format Language.

I will look at layout guides another time but for now here are my notes on using layout anchors to create constraints in code without the pain:

Updated 24-Nov-2016 to latest Swift 3 naming conventions

Creating Constraints the Painful Way

First a reminder of the painful way to create constraints using the NSLayoutConstraint class methods. Assume we have a stack view we want to pin to the left and right margins of the top-level view of a view controller:

NSLayoutConstraint(item: stackView,
  attribute: .leading,
  relatedBy: .equal,
  toItem: view,
  attribute: .leadingMargin,
  multiplier: 1,
  constant: 0).isActive = true

NSLayoutConstraint(item: stackView,
  attribute: .trailing,
  relatedBy: .equal,
  toItem: view,
  attribute: .trailingMargin,
  multiplier: 1,
  constant: 0).isActive = true

We can also pin the stack view below the top layout guide so that it does not get hidden by a navigation bar:

NSLayoutConstraint(item: stackView,
  attribute: .top,
  relatedBy: .equal,
  toItem: topLayoutGuide,
  attribute: .bottom,
  multiplier: 1,
  constant: 8.0).isActive = true

I think we can agree that is neither pretty nor easy to understand (the Objective-C version is even worse). Using the Visual Format Language is not much better in my opinion:

let views: [String: AnyObject] =
  ["stackView" : stackView,
   "topLayoutGuide" : topLayoutGuide]

let h = NSLayoutConstraint.constraints(
  withVisualFormat: "|-[stackView]-|",
  options: [],
  metrics: nil,
  views: views)
NSLayoutConstraint.activate(h)

let v = NSLayoutConstraint.constraints(
  withVisualFormat: "V:|[topLayoutGuide]-[stackView]",
  options: [],
  metrics: nil,
  views: views)
NSLayoutConstraint.activate(v)

Creating Constraints with Layout Anchors

Layout anchors make it much easier to create constraints. From the class documentation:

The NSLayoutAnchor class is a factory class for creating NSLayoutConstraint objects using a fluent API. Use these constraints to programatically define your layout using Auto Layout.

Layout anchors are properties on a UIView (or UILayoutGuide). Each property is a subclass of NSLayoutAnchor with methods to directly create constraints to other layout anchors of the same type. A UIView has twelve different layout anchor properties you can use to create horizontal, vertical or size-based constraints:

Horizontal Constraints

Layout Anchors of type NSLayoutXAxisAnchor for creating horizontal constraints:

For example, to create a constraint to center align two views:

// Swift
myView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true

// Objective-C
[self.myView.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor].active = YES;

Note how you start with an anchor on one view and create the constraint to an anchor on another view.

Vertical constraints

Layout Anchors of type NSLayoutYAxisAnchor for creating vertical constraints:

For example, to create a constraint between the top and bottom anchors of two views with a constant spacing:

// Swift
myView.bottomAnchor.constraint(equalTo: view.topAnchor,
       constant: 8).isActive=true

// Objective-C
[self.myView.bottomAnchor constraintEqualToAnchor:self.view.topAnchor
      constant:8.0].active = YES;

Size-Based Constraints

Layout Anchors of type NSLayoutDimension for creating size-based constraints:

For example, to create a width constraint for a view:

// Swift
myView.widthAnchor.constraint(equalToConstant: 50.0).isActive = true

// Objective-C
[self.myView.widthAnchor constraintEqualToConstant:50.0].active = YES;

A further example, to make the height of a view twice the height of another view using a multiplier:

// Swift
myView.heightAnchor.constraint(equalTo: otherView.heightAnchor,
       multiplier: 2.0).isActive = true

// Objective-C
[self.myView.heightAnchor constraintEqualToAnchor:self.otherView.heightAnchor 
      multiplier:2.0].active = YES;

View margins

A UIView does not have layout anchors for the leading and trailing margins we used when creating our stack view constraints. Instead iOS 9 added two new properties, layoutMarginGuide and readableContentGuide, which in turn have layout anchors.

For example, to constrain the leading edge of a subview to the leading margin of the superview:

// Swift
let margins = view.layoutMarginsGuide    
myView.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true

// Objective-C
UILayoutGuide *margins = self.view.layoutMarginsGuide;
[self.myView.leadingAnchor constraintEqualToAnchor:
      margins.leadingAnchor].active = YES;

Top and Bottom Layout Guides

A view controller has both a topLayoutGuide and bottomLayoutGuide property for when you want to position content relative to top or bottom UIKit toolbars. Starting with iOS 9, both properties conform to the UILayoutSupport protocol which gives us bottomAnchor, topAnchor and heightAnchor properties for the bar.

For example, to position a view 8 points below the bottom of the top layout guide:

// Swift
myView.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor,
       constant: 8.0).isActive = true

// Objective-C
[self.stackView.topAnchor constraintEqualToAnchor:self.topLayoutGuide.bottomAnchor
       constant:8.0].active = YES;

Putting It All Together

So how would we create our stack view constraints using layout anchors?

First we get the leading and trailing margins of the superview:

let margins = view.layoutMarginsGuide

Then we create the leading and trailing horizontal constraints:

stackView.leadingAnchor.constraint(equalTo: margins.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: margins.trailingAnchor).isActive = true

Finally we use the topLayoutGuide property of the view controller to pin our stack view 8 points below the navigation bar:

stackView.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor,
          constant: 8.0).isActive = true

The Objective-C version is slightly more verbose but still a big improvement:

UILayoutGuide *margins = self.view.layoutMarginsGuide;

[self.stackView.leadingAnchor
      constraintEqualToAnchor:margins.leadingAnchor].active = YES;
[self.stackView.trailingAnchor
      constraintEqualToAnchor:margins.trailingAnchor].active = YES;

[self.stackView.topAnchor
      constraintEqualToAnchor:self.topLayoutGuide.bottomAnchor
      constant:8.0].active = YES;

I find it much easier to understand the intent of these constraints compared to the earlier code.

Sample Code

You can find the Swift code snippets from this post in a sample Auto Layout Xcode project in my GitHub Code Examples repository.

Further Reading

Never miss a post!

iOS Size Classes Cheat Sheet

Subscribe and get my free iOS Size Classes Cheat Sheet

Unsubscribe at any time.
No time to watch WWDC videos?

Sign up to get my iOS posts direct to your inbox and I will send you a free PDF of my iOS Size Classes Cheat Sheet.

Unsubscribe at any time.
Archives Categories
comments powered by Disqus