Layout Anchors
Apple’s recommended way of defining layouts for most UIs is to use Auto Layout. Originally introduced all the way back in iOS 6 — at a time when we only had a small number of screen sizes to support — Auto Layout has undergone quite a lot of changes and improvements over the years, in particular with the introduction of layout anchors in iOS 9.
When building layouts using Auto Layout, we no longer manually calculate the positions and sizes of our views — and instead use a constraint-based API to have the system perform those calculations on our behalf, by evaluating the constraints we have defined. Since this is done automatically (hence the name) whenever a layout condition — such as the rotation of the device — changes, supporting all the various devices iOS and macOS runs on becomes much easier.
Since constraints are both very powerful and complex, the “raw” API for dealing with them is quite verbose and somewhat cumbersome to work with. For example, here’s how we’d make a label’s height match the height of a button, by defining a constraint ourselves:
let constraint = NSLayoutConstraint(
item: label,
attribute: .height,
relatedBy: .equal,
toItem: button,
attribute: .height,
multiplier: 1,
constant: 0
)
Thankfully, layout anchors make the above kind of task a lot easier. Each UIView
on iOS and NSView
on macOS contains a series of anchors that can be used to automatically create constraints relative to other views. For example, here’s how the above constraint can be defined by using the heightAnchor
of both views:
let constraint = label.heightAnchor.constraint(
equalTo: button.heightAnchor
)
Much nicer, right? 😀 However, just like manually created constraints, the constraints created using layout anchors also need to be activated in order to be evaluated by the Auto Layout engine. That can either be done by setting the isActive
property to true
, or by using the activate
API on NSLayoutConstraint
to activate an array of constraints in one go:
// Both of these are valid ways to activate a constraint
constraint.isActive = true
NSLayoutConstraint.activate([constraint])
The beauty of Auto Layout is that it’s not only capable of making simple 1:1 relationships between metrics, but can be used to create really complex layouts as well. For example, here we position our button from before at the center of its parent view — while also giving our label a minimum width and placing it beneath the button:
NSLayoutConstraint.activate([
// Place the button at the center of its parent
button.centerXAnchor.constraint(equalTo: parent.centerXAnchor),
button.centerYAnchor.constraint(equalTo: parent.centerYAnchor),
// Give the label a minimum width based on the button’s width
label.widthAnchor.constraint(greaterThanOrEqualTo: button.widthAnchor),
// Place the label 20 points beneath the button
label.topAnchor.constraint(equalTo: button.bottomAnchor, constant: 20),
label.centerXAnchor.constraint(equalTo: button.centerXAnchor)
])
As you can see above, we’re free to mix and match anchors — for example constraining the top
anchor of our label to the bottom
anchor to the button — as long as they are within the same dimension. That is, we can pin top anchors to bottom ones and vice versa, but not any vertical anchors to any horizontal ones. The same goes for positional anchors such as leading
, centerX
, and right
as well.
While layout anchors have vastly improved the user friendliness of Auto Layout, it’s still quite common to run into a few issues — especially in the beginning. So if your layout doesn’t initially look as you’d expect, here’s a few things to look out for:
- By default, all views automatically translate their initial auto resizing masks into layout constraints — which can conflict with the constraints we’ve defined in our code. To disable this behavior, simply set
translatesAutoresizingMaskIntoConstraints
tofalse
on the view in question. - In order to define constraints based on anchors from multiple views, all views involved need to be a part of the same view hierarchy (otherwise an exception will be thrown at runtime). An easy way to assure that is to have all views that share the same layout code also share the same superview.
- A constraint can be dynamically enabled and disabled — for example to adapt to changes like rotation or different UI states — by toggling that constraint’s
isActive
property. In general, it’s more performant to activate/deactivate a constraint rather than adding/removing it from its view.
Auto Layout will most likely continue to be a system that offers a great deal of power and flexibility, but at the cost of slightly increased complexity compared to when doing layout calculations manually. However, like with all things complex, it’s all about getting started — and things should eventually become much more clear.
Thanks for reading! 🚀