Pain Free Constraints with Layout AnchorsFeb 22, 2016 · 5 minute read · Comments
Auto LayoutiOS 9
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
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:
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.
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;
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;
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,
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
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
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.
You can find the Swift code snippets from this post in a sample Auto Layout Xcode project in my GitHub Code Examples repository.