Power-Up Your Anchors - Part 1

Programmatically done Auto Layout is still the preferred way of implementing views by many developers. While there are amazing open-source frameworks, most of them differ from Apple’s anchor syntax. Therefore, by adding them to your project, you’ll raise the entry level complexity of your project and increase its learning curve. In this article, you’ll learn how to avoid adding an external dependency and create your own layer above NSLayoutAnchor to solve some of its issues.
"While the NSLayoutAnchor class provides additional type checking, it is still possible to create invalid constraints. For example, the compiler allows you to constrain one view’s leadingAnchor with another view’s leftAnchor, since they are both NSLayoutXAxisAnchor instances. However, Auto Layout does not allow constraints that mix leading and trailing attributes with left or right attributes”.
First of all, take a look at the following code and try to identify some of NSLayoutAnchor’s boilerplate code.
"If this property’s value is true, the system creates a set of constraints that duplicate the behaviour specified by the view’s authorising mask. This also lets you modify the view’s size and location using the view’s frame, bounds, or centre properties.
If you want to use Auto Layout to dynamically calculate the size and position of your view, you must set this property to false".
This describes exactly what you want: use Auto Layout to calculate the size and position of your views dynamically. However, you don’t want to write this huge property for every single view, not even inside a forEach.
There are multiple approaches to solve this, but for now, you will be improving addSubview and adding a side-effect to it. To do so, create an UIView extension with the following code:
Now, instead of doing the following:

Prior to Swift 4, you were forced to extend each of NSLayoutAnchor’s subclasses, or constrain it, because it is a generic class, but now you can simply expose your extension to Objective-C.
One way of solving this would be to set isActive to true by default. You can achieve this with the following:
By using @discardableResult, you will be returning an NSLayoutConstraint that you can safely ignore if you don’t need it. In case you never heard about this keyword, I’ve written an article titled "I Want to be Discardable” that addresses this.
According to Apple’s documentation, NSLayoutAnchor exposes 6 different functions- These are as follows:
But this time it is pretty easy to understand what’s happening. Currently, your function is expecting you to send an NSLayoutAnchor.
To solve this, the following are the two most obvious solutions:
To avoid missing cases that already exist, you should also have the option to apply a constraint between two NSLayouDimension anchors with a constant & multiplier.
In order to address this, you must enable this feature only for NSLayoutDimension’s anchors. Therefore, you are going to extend NSLayoutDimension with the following code:
You'll now be able to apply width and height constraints without any kind of relation to another anchor.
So, now that we've addressed setting translatesAutoresizingMaskIntroConstraints, relations and activating constraints, let's work on priorities and DRY in our part 2.
Introduction
NSLayoutAnchor was first introduced by Apple in iOS 9.0 and it’s described as a "factory class for creating layout constraint objects using a fluent API”. Apple’s documentation also refers that NSLayoutAnchor usage is preferred when compared to NSLayoutConstraint: "use these constraints to programmatically define your layout using Auto Layout. Instead of creating NSLayoutConstraint …”. This is due to type checking and having a simpler and cleaner interface when compared to NSLayoutConstraint.
Improvements related to type checking are based in Apple’s decision to split NSLayoutAnchor into three different concepts, being:- NSLayoutXAxisAnchor for horizontal constraints.
- NSLayoutYAxisAnchor for vertical constraints.
- NSLayoutDimension for width and height constraints.
"While the NSLayoutAnchor class provides additional type checking, it is still possible to create invalid constraints. For example, the compiler allows you to constrain one view’s leadingAnchor with another view’s leftAnchor, since they are both NSLayoutXAxisAnchor instances. However, Auto Layout does not allow constraints that mix leading and trailing attributes with left or right attributes”.
First of all, take a look at the following code and try to identify some of NSLayoutAnchor’s boilerplate code.
// Subviews
let logoImageView = UIImageView()
let welcomeLabel = UILabel()
let dismissButton = UIButton()
According to this implementation, you should have found the following requirements:
// Add Subviews & Set view's translatesAutoresizingMaskIntoConstraints to false
[logoImageView, welcomeLabel, dismissButton].forEach {
self.addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false}
// Set Constraints
logoImageView.topAnchor.constraint(equalTo: topAnchor, constant: 12).isActive = true
logoImageView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
logoImageView.widthAnchor.constraint(equalToConstant: 50).isActive = true
logoImageView.heightAnchor.constraint(equalToConstant: 50).isActive = true
dismissButton.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: 12).isActive = true
dismissButton.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor, constant: -12).isActive = true
dismissButton.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
let dismissButtonWidth = dismissButton.widthAnchor.constraint(equalToConstant: 320)
dismissButtonWidth.priority = UILayoutPriority(UILayoutPriority.defaultHigh.rawValue + 1)
dismissButtonWidth.isActive = true
welcomeLabel.topAnchor.constraint(equalTo: logoImageView.bottomAnchor, constant: 12).isActive = true
welcomeLabel.bottomAnchor.constraint(greaterThanOrEqualTo: dismissButton.topAnchor, constant: 12).isActive = true
welcomeLabel.leadingAnchor.constraint(equalTo: dismissButton.leadingAnchor).isActive = true
welcomeLabel.trailingAnchor.constraint(equalTo: dismissButton.trailingAnchor).isActive = true
- You must set translatesAutoresizingMaskIntoConstraints to false for every view;
- You must activate constraints by setting its property isActive to true or by using NSLayoutConstraint.activate();
- You cannot set UILayoutPriority via a parameter and must instead create a variable.
TranslatesAutor… Yes, that long property you always set to false
Here lies the first identified issue, and Apple is clear on why you always have to set it as false:"If this property’s value is true, the system creates a set of constraints that duplicate the behaviour specified by the view’s authorising mask. This also lets you modify the view’s size and location using the view’s frame, bounds, or centre properties.
If you want to use Auto Layout to dynamically calculate the size and position of your view, you must set this property to false".
This describes exactly what you want: use Auto Layout to calculate the size and position of your views dynamically. However, you don’t want to write this huge property for every single view, not even inside a forEach.
There are multiple approaches to solve this, but for now, you will be improving addSubview and adding a side-effect to it. To do so, create an UIView extension with the following code:
extension UIView {With this code, you will be able to send multiple views, set them all as subviews and set each one’s translatesAutoresizingMaskIntoConstraints to false all at once.
func addSubviewsUsingAutoLayout(_ views: UIView ...) {
subviews.forEach {
self.addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false
}
}
}
Now, instead of doing the following:
[logoImageView, welcomeLabel, dismissButton].forEach {You will now have:
self.addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false
}
self.addSubviewsUsingAutoLayout(logoImageView, welcomeLabel, dismissButton)
What about the remaining issues?
Start by extending NSLayoutAnchor as follows:extension NSLayoutAnchor {This will generate an error:
func test() {}
}

Prior to Swift 4, you were forced to extend each of NSLayoutAnchor’s subclasses, or constrain it, because it is a generic class, but now you can simply expose your extension to Objective-C.
@objc extension NSLayoutAnchorIf you try to compile this, you will notice that the error disappears. Your extension is now ready to use your own code, and that is exactly what you’re going to do.
Activate your constraints!
Setting isActive in every single anchor is excruciating. Even though NSLayoutConstraint.activate() might be considered a better option, according to Apple’s documentation, it still adds a lot of indentation.One way of solving this would be to set isActive to true by default. You can achieve this with the following:
@objc extension NSLayoutAnchor {This function uses a Swift capability called default parameter. It allows isActive to be called as an optional argument. By default, it will always be set to true. However, in case you don’t want it active, you can set it to false.
@discardableResult
func constrain(equalTo anchor: NSLayoutAnchor,
with constant: CGFloat = 0.0,
isActive: Bool = true) -> NSLayoutConstraint {
let constraint = self.constraint(equalTo: anchor, constant: constant)
constraint.isActive = isActive
return constraint
}
}
By using @discardableResult, you will be returning an NSLayoutConstraint that you can safely ignore if you don’t need it. In case you never heard about this keyword, I’ve written an article titled "I Want to be Discardable” that addresses this.
Too many functions!
Currently, you can only support a relation of equalTo when you should also support greaterThanOrEqualTo and lessThanOrEqualTo.According to Apple’s documentation, NSLayoutAnchor exposes 6 different functions- These are as follows:
- func constraint(equalTo: NSLayoutAnchor) -> NSLayoutConstraint
- func constraint(equalTo: NSLayoutAnchor, constant: CGFloat) -> NSLayoutConstraint
- func constraint(greaterThanOrEqualTo: NSLayoutAnchor) -> NSLayoutConstraint
- func constraint(greaterThanOrEqualTo: NSLayoutAnchor, constant: CGFloat) -> NSLayoutConstraint
- func constraint(lessThanOrEqualTo: NSLayoutAnchor) -> NSLayoutConstraint
- func constraint(lessThanOrEqualTo: NSLayoutAnchor, constant: CGFloat) -> NSLayoutConstraint
@objc extension NSLayoutAnchor {If you try to use it:
@discardableResult
func constrain(_ relation: NSLayoutConstraint.Relation = .equal,
to anchor: NSLayoutAnchor,
with constant: CGFloat = 0.0,
isActive: Bool = true) -> NSLayoutConstraint {
let constraint: NSLayoutConstraint
switch relation {
case .equal:
constraint = self.constraint(equalTo: anchor, constant: constant)
case .greaterThanOrEqual:
constraint = self.constraint(greaterThanOrEqualTo: anchor, constant: constant)
case .lessThanOrEqual:
constraint = self.constraint(lessThanOrEqualTo: anchor, constant: constant)
}
constraint.isActive = isActive
return constraint
}
}
let a = UIView()Everything seems to be working well. But what if you try to apply a width constraint based on a constant? Add the following line of code to the previous example and rebuild:
let b = UIView()
a.addSubviewsUsingAutoLayout(b)
// Constraint set as equal to a.topAnchor
b.topAnchor.constrain(a.topAnchor)
// Constraint set as greater than or equal to a.bottomAnchor
b.bottomAnchor.constrain(.greaterThanOrEqual, to: a.bottomAnchor)
// Constraint set as less than or equal to a.bottomAnchor
b.leadingAnchor.constrain(.lessThanOrEqual, to: a.leadingAnchor)
b.widthAnchor.constrain(to: 50.0)Oh no, you trigger another error:

To solve this, the following are the two most obvious solutions:
- Set the expecting anchor as an optional, NSLayoutAnchor?.
- Provide a default parameter.
- It would lead to some edge cases that aren’t supposed to be possible and wouldn’t even work. Therefore your interface would allow inconsistencies that didn’t exist before.
- It isn’t possible to provide a valid default parameter to NSLayoutAnchor.
To avoid missing cases that already exist, you should also have the option to apply a constraint between two NSLayouDimension anchors with a constant & multiplier.
In order to address this, you must enable this feature only for NSLayoutDimension’s anchors. Therefore, you are going to extend NSLayoutDimension with the following code:
extension NSLayoutDimension {
@discardableResult
func constrain(_ relation: NSLayoutConstraint.Relation = .equal,
to anchor: NSLayoutDimension,
with constant: CGFloat = 0.0,
multiplyBy multiplier: CGFloat = 1.0,
isActive: Bool = true) -> NSLayoutConstraint {
let constraint: NSLayoutConstraint
switch relation {
case .equal:
constraint = self.constraint(equalTo: anchor, multiplier: multiplier, constant: constant)
case .greaterThanOrEqual:
constraint = self.constraint(greaterThanOrEqualTo: anchor, multiplier: multiplier, constant: constant)
case .lessThanOrEqual:
constraint = self.constraint(lessThanOrEqualTo: anchor, multiplier: multiplier, constant: constant)
}
constraint.isActive = isActive
return constraint
}
@discardableResult
func constrain(_ relation: NSLayoutConstraint.Relation = .equal,
to constant: CGFloat = 0.0,
isActive: Bool = true) -> NSLayoutConstraint {
let constraint: NSLayoutConstraint
switch relation {
case .equal:
constraint = self.constraint(equalToConstant: constant)
case .greaterThanOrEqual:
constraint = self.constraint(greaterThanOrEqualToConstant: constant)
case .lessThanOrEqual:
constraint = self.constraint(lessThanOrEqualToConstant: constant)
}
constraint.isActive = isActive
return constraint
}
}
You'll now be able to apply width and height constraints without any kind of relation to another anchor.
So, now that we've addressed setting translatesAutoresizingMaskIntroConstraints, relations and activating constraints, let's work on priorities and DRY in our part 2.