Painless NSLayoutAnchors
From simple login screens where you have two text fields and a button to more complex screens with nested stack views, custom collection view cells where there is also a need to implement animations improving user experience.
Positioning views can be done in a few ways:
- Manually setting views frame position
- Storyboards
- Layout constraints created in code
The first option is probably used only by masochists. Storyboards are ideal for prototyping and building small apps. Collaboration when using storyboards can be very hard and
Some of us may remember times when a common way of adding layout constraints was using Visual Format Language. For those who do not remember what was that about, here is a “simple” snippet:
let verticalConstraints = NSLayoutConstraint.constraints(
withVisualFormat: "V:|-40-[logoImageView(50)]-[loginTextField]-20-[passwordTextField]-20-[loginButton]-15@750-|",
options: [],
metrics: nil,
views: views
)
Simple? Not at all. It’s not very descriptive. It takes a huge amount of time to master VFL. Because of that and a fact that NSLayoutConstraint API was not very friendly, a lot of libraries for adding constraints were created. You probably heard about PureLayout, SnapKit, Masonry, EasyPeasy and many many others. All of those were trying to make adding constraints easier. I used some of those and none of them made me spend a lot of time to learn how to create constraints using those.
Some time ago, Apple decided to improve our lives and introduced NSLayoutAnchor. At first glance, it looks very nice. Every view have the same set of anchors that we can use for creating constraints. Let's take a look at the example:
let topConstraint = logoView.topAnchor.constraint(equalTo: self.topAnchor, constant: 50)
let heightConstraint = emailTextField.heightAnchor.constraint(equalToConstant: 100)
let widthConstraint = emailTextField.widthAnchor.constraint(equalToConstant: 100)
So, where is the pain?
Actually, that’s not all the code you need to write. To make it work you need to set translatesAutoresizingMaskIntoConstraints
self.translatesAutoresizingMaskIntoConstraints = false
logoView.translatesAutoresizingMaskIntoConstraints = false
logoView.topAnchor.constraint(equalTo: self.topAnchor, constant: 50).isActive = true
logoView.topAnchor.constraint(equalTo: self.topAnchor, constant: 50).isActive = true
logoView.heightAnchor.constraint(equalToConstant: 100).isActive = true
logoView.widthAnchor.constraint(equalToConstant: 100).isActive = true
And remember UIKit will not help you. If you will forget about setting
How can we deal with layouts without using any external dependency and without all NSLayoutAnchor
If Chris did all the necessary work, where is my part here?
loginTextField.addConstraints([
equal(\.heightAnchor, to: 30),
equal(logoImageView, \.topAnchor, \.bottomAnchor, constant: 20),
equal(superView, \.leadingAnchor, constant: 20),
equal(superView, \.trailingAnchor, constant: -20)
])
What does this do is:
- makes view 30pts height
- attaches top border to bottom border of logoView with 20pts spacing
- attaches leading and trailing anchors to corresponding anchors of superView with offset 20pts
I hope that without my explanation you could easily tell how this works. If yes, then
For more info please refer to the code and documentation for the wrapper. If you want to play with this, I’ve prepared swift playground with one simple view created.