Power-Up Your Anchors - Part 2

We've addressed setting translatesAutoresizingMaskIntoConstraints, relations and activating constraints in our part 1 (if you haven't read it yet, please be invited to read it here), and now let's work on priorities and DRY.
Priorities
To set a constraint’s priority in the current model, with anchors, you would need to do the following:let bWidth = b.widthAnchor.constraint(equalToConstant: 50.0) bWidth.priority = .defaultHigh // or if you want between .defaultHigh and .required bWidth.priority = UILayoutPriority(UILayoutPriority.defaultHigh.rawValue + 1)A good way to avoid being forced to assign the constraint to a property is adding this option to your functions. Also, with a default value of .required, you don’t need to type it in most scenarios.
Replace your functions with the following:
@objc extension NSLayoutAnchor { @discardableResult func constrain(_ relation: NSLayoutConstraint.Relation = .equal, to anchor: NSLayoutAnchor, with constant: CGFloat = 0.0, prioritizeAs priority: UILayoutPriority = .required, 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.priority = priority constraint.isActive = isActive return constraint } } extension NSLayoutDimension { @discardableResult func constrain(_ relation: NSLayoutConstraint.Relation = .equal, to anchor: NSLayoutDimension, with constant: CGFloat = 0.0, multiplyBy multiplier: CGFloat = 1.0, prioritizeAs priority: UILayoutPriority = .required, 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.priority = priority constraint.isActive = isActive return constraint } @discardableResult func constrain(_ relation: NSLayoutConstraint.Relation = .equal, to constant: CGFloat = 0.0, prioritizeAs priority: UILayoutPriority = .required, 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.priority = priority constraint.isActive = isActive return constraint } }
What if you wanted a priority between .defaultHigh and .required? The current way doesn’t look very clean. In order to improve it, you are going to extend NSLayoutPriority with the following:
extension UILayoutPriority { static func +(lhs: UILayoutPriority, rhs: Float) -> UILayoutPriority { return UILayoutPriority(lhs.rawValue + rhs) } static func -(lhs: UILayoutPriority, rhs: Float) -> UILayoutPriority { return UILayoutPriority(lhs.rawValue - rhs) } }
And now you’ll be able to do the following:
bWidth.priority = .defaultHigh + 1
DRY
DRY stands for "Don’t Repeat Yourself,” and it is often seen as a golden rule in software development.You are currently repeating yourself when setting isActive and priority. In order to follow this pattern, you will move this to an NSLayoutConstraint extension.
Start by adding the following function to your extension:
func set(priority: UILayoutPriority, isActive: Bool) { self.priority = priority self.isActive = isActive }And now update your functions with the following:
@objc extension NSLayoutAnchor { @discardableResult func constrain(_ relation: NSLayoutConstraint.Relation = .equal, to anchor: NSLayoutAnchor, with constant: CGFloat = 0.0, prioritizeAs priority: UILayoutPriority = .required, isActive: Bool = true) -> NSLayoutConstraint { var 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.set(priority: priority, isActive: isActive) return constraint } } extension NSLayoutDimension { @discardableResult func constrain(_ relation: NSLayoutConstraint.Relation = .equal, to anchor: NSLayoutDimension, with constant: CGFloat = 0.0, multiplyBy multiplier: CGFloat = 1.0, prioritizeAs priority: UILayoutPriority = .required, 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.set(priority: priority, isActive: isActive) return constraint } @discardableResult func constrain(_ relation: NSLayoutRelation = .equal, to constant: CGFloat = 0.0, prioritizeAs priority: UILayoutPriority = .required, isActive: Bool = true) -> NSLayoutConstraint { var 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.set(priority: priority, isActive: isActive) return constraint } }
What have you done?
At the beginning of this article, you spotted some problems related to anchors. Now you can proudly check that you’ve developed solutions to each of them.- Having to set translatesAutoresizingMaskIntoConstraints to false for every view that isn’t loaded from a NIB.
- You’ve created a new addSubview function that supports sending multiple UIView
- Activating constraints by setting its property isActive to true, or using NSLayoutConstraint.activate()
- Setting UILayoutPriority via parameter is not supported and requires you to create a variable.
- NSLayoutAnchor extensions work together to solve these two problems.
- Interoperability has its costs to NSLayoutAnchor because it doesn’t allow it to take advantage of some Swift capabilities.
- You’ve reduced the number of functions needed with an enum based approach and adopted default parameters in your interface.
How does it look?
Before showcasing your new and fresh powered-up anchors, take another look at the initial example shown in this article:// Subviews let logoImageView = UIImageView() let welcomeLabel = UILabel() let dismissButton = UIButton() // 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 = trueNow you can achieve the same result in a much cleaner way with your extensions:
// Subviews let logoImageView = UIImageView() let welcomeLabel = UILabel() let dismissButton = UIButton() // Add Subviews & Set view's translatesAutoresizingMaskIntoConstraints to false addSubviewsUsingAutoLayout(logoImageView, welcomeLabel, dismissButton) // Set Constraints logoImageView.topAnchor.constrain(to: topAnchor, with: 12) logoImageView.centerXAnchor.constrain(to: centerXAnchor) logoImageView.widthAnchor.constrain(to: 50) logoImageView.heightAnchor.constrain(to: 50) dismissButton.leadingAnchor.constrain(.greaterThanOrEqual, to: leadingAnchor, with: 12) dismissButton.trailingAnchor.constrain(.lessThanOrEqual, to: trailingAnchor, with: -12) dismissButton.bottomAnchor.constrain(to: bottomAnchor) dismissButton.widthAnchor.constrain(to: 320, prioritizeAs: .defaultHigh + 1) welcomeLabel.topAnchor.constrain(to: logoImageView.bottomAnchor, with: 12) welcomeLabel.bottomAnchor.constrain(.greaterThanOrEqual, to: dismissButton.topAnchor, with: 12) welcomeLabel.leadingAnchor.constrain(to: dismissButton.leadingAnchor) welcomeLabel.trailingAnchor.constrain(to: dismissButton.trailingAnchor)As you can see, it is a lot easier to read and understand what each anchor is doing. You were also able to decrease the amount of code you needed to write.
Future Improvements
While this will simplify the usage of Auto Layout programmatically, there are some details missing. The following functions aren’t covered:- constraintEqualToSystemSpacingBelow(NSLayoutYAxisAnchor, multiplier: CGFloat)
- constraintGreaterThanOrEqualToSystemSpacingBelow(NSLayoutYAxisAnchor, multiplier: CGFloat)
- constraintLessThanOrEqualToSystemSpacingBelow(NSLayoutYAxisAnchor, multiplier: CGFloat)
- constraintEqualToSystemSpacingAfter(NSLayoutXAxisAnchor, multiplier: CGFloat)
- constraintGreaterThanOrEqualToSystemSpacingAfter(NSLayoutXAxisAnchor, multiplier: CGFloat)
- constraintLessThanOrEqualToSystemSpacingAfter(NSLayoutXAxisAnchor, multiplier: CGFloat)
The End
If you’ve managed to fully read this and reach the end, congratulations on powering your anchors!In the end, what’s your opinion of it? Did you know it already, or was it something new to you? Let me know, alongside with your questions, by sending feedback on Twitter.
Thanks for reading!
Last but not least, I would like to praise Ana Filipa Ferreira, João Pereira, José Figueiredo, Pedro Eusébio and Tiago Silva for their outstanding support.