Give your simulator superpowers

RocketSim: An Essential Developer Tool
as recommended by Apple

Auto Layout in Swift: Writing constraints programmatically

Auto Layout constraints allow us to create views that dynamically adjust to different size classes and positions. The constraints will make sure that your views adjust to any size changes without having to manually update frames or positions.

Can you imagine a world without Auto Layout? We used to calculate frames ourselves or we used auto resizing masks (yes, that is related to the translatesAutoresizingMaskIntoConstraints property). It’s only been since iOS 6 that we can depend on those dynamic adjusting constraints.

By using those extensions you prevent yourself from writing a lot of boilerplate code.

There’s a lot to cover but before I dive in I want to shortly discuss the difference between writing constraints in code vs using the Interface Builder.

Interface builder vs programmatically writing Auto Layout constraints

Writing Auto Layout constraints programmatically vs using the Interface Builder (IB), it’s always a hot topic. I decided to ask my followers with a poll on Twitter which resulted in 851 votes and a lot of replies with strong opinions.

Although there is a winner the results are very close and I’m quite surprised by the spread. It confirms the fact that it’s a hot topic as developers clearly don’t agree with one solution. You might think that we can be happy with the idea that SwiftUI will solve this after all.

Either way, we’re not in a world yet where we can completely rely on SwiftUI. It’s still often a decision to make whether you go for writing your layout in code or going for storyboards or XIBs.

Following personal experience and the comments on Twitter there’s a few pros and cons of writing your layout in code.

Pros and cons for writing Auto Layout constraints in code

Pros

  • Easier to merge in GIT
  • The code is easier to debug
  • Constraints might be easier to overview

Cons

  • There’s no visual representations. The IB allows you to see your view in different size classes.
  • You might end up with a lot of layout code in your view controllers

Both solutions have quite a learning curve although it might be easier to write constraints in code if you’ve had experience with setting up constraints in the Interface Builder. Therefore, I would personally recommend to always start with Storyboards if you just get started with building apps.

Combining the Interface Builder with constraints in code could be a solution as well. However, keep in mind that this is less consistent and might make it even harder to understand what’s going on. Writing constraints while using the Interface Builder as well could be seen as writing Auto Layout side effects.

If you’re working in teams you might want to prevent yourself from solving a lot of merge conflicts by writing your constraints programmatically. In the end, it’s mostly personal taste. Just like the poll results show there’s no golden way.

Writing Auto Layout constraints in code

Once you decided to write your constraints in code you enter a world with a lot of possibilities. Luckily enough, these type of constraints are no longer needed:

NSLayoutConstraint.constraints(withVisualFormat: "H:|-[icon(==postDate)]-20-[titleLabel(120@250)]-20@750-[postDate(>=50)]-|", metrics: nil, views: views)

It’s an example constraint written in the Auto Layout Visual Format Language. It’s still supported and it might be something you encounter when opening an old project but it’s not something I’ll cover in this blog post.

Instead, we’ll focus on using Layout Anchors as introduced in iOS 9. It’s a great API that, nowadays, allows you to write constraints in code quite easily.

Writing constraints by using Layout Anchors

First of all, we need to set the translatesAutoresizingMaskIntoConstraints to false. This is to prevent the view’s auto-resizing mask to be translated into Auto Layout constraints and affecting your constraints.

After that, you start by creating an array of constraints. In this array, you define your set of constraints:

let constraints = [
    view.centerXAnchor.constraint(equalTo: superview.centerXAnchor),
    view.centerYAnchor.constraint(equalTo: superview.centerYAnchor),
    view.widthAnchor.constraint(equalToConstant: 100),
    view.heightAnchor.constraint(equalTo: view.widthAnchor)
]
NSLayoutConstraint.activate(constraints)

This example shows the basics of writing constraints and should be both readable and understandable. It creates a square and centers it in its superview. The last line is needed to actually activate the constraints so they make your layout appear as expected.

Each UIView comes with a collection of anchor properties that allow you to set up relations between views:

extension UIView {

    /* Constraint creation conveniences. See NSLayoutAnchor.h for details.
     */
    open var leadingAnchor: NSLayoutXAxisAnchor { get }

    open var trailingAnchor: NSLayoutXAxisAnchor { get }

    open var leftAnchor: NSLayoutXAxisAnchor { get }

    open var rightAnchor: NSLayoutXAxisAnchor { get }

    open var topAnchor: NSLayoutYAxisAnchor { get }

    open var bottomAnchor: NSLayoutYAxisAnchor { get }

    open var widthAnchor: NSLayoutDimension { get }

    open var heightAnchor: NSLayoutDimension { get }

    open var centerXAnchor: NSLayoutXAxisAnchor { get }

    open var centerYAnchor: NSLayoutYAxisAnchor { get }

    open var firstBaselineAnchor: NSLayoutYAxisAnchor { get }

    open var lastBaselineAnchor: NSLayoutYAxisAnchor { get }
}

Each Anchor it returns subclasses from NSLayoutAnchor which comes with a few common methods to set relationships. These include equal to, greater than, and less than or equal to relationships. It’s best to explore its documentation and get yourself familiar with the available APIs.

Order of constraints

Once you start writing your constraints its important to keep in mind the order of your constraints if you start working with constants.

Taking the following set of constraint:

let constraints = [
    innerSquare.topAnchor.constraint(equalTo: outerSquare.topAnchor),
    innerSquare.leftAnchor.constraint(equalTo: outerSquare.leftAnchor, constant: 40),
    outerSquare.bottomAnchor.constraint(equalTo: innerSquare.bottomAnchor),
    outerSquare.rightAnchor.constraint(equalTo: innerSquare.rightAnchor, constant: 40)
]

NSLayoutConstraint.activate(constraints)

Results in the following image in which the outer square is blue and the inner square is red:

A simple Auto Layout in code example
A simple Auto Layout in code example

If we would change the order of the outer square and inner square the constants need to be set with negative values:

let constraints = [
    innerSquare.topAnchor.constraint(equalTo: outerSquare.topAnchor),
    outerSquare.leftAnchor.constraint(equalTo: innerSquare.leftAnchor, constant: -40),
    outerSquare.bottomAnchor.constraint(equalTo: innerSquare.bottomAnchor),
    innerSquare.rightAnchor.constraint(equalTo: outerSquare.rightAnchor, constant: -40)
]

NSLayoutConstraint.activate(constraints)

The same counts for the top and bottom constraints. Therefore, keep in mind the following order when setting constraints and switch the view and superview for the last two anchors:

  • Top
  • Left
  • Bottom
  • Right

This basically comes down to counterclockwise.

Available layout guides

Each UIView comes with a few layout guides which can also be used as anchors.

  • layoutMarginsGuide: Set constraints and keep the layout margins as spacing
  • readableContentGuide: Constraints the width to a size that is easy for the user to read
  • safeAreaLayoutGuide: Represents the portion of your view that is unobscured by bars and other content

You could use the guides as following:

let constraints = [
    outerSquare.topAnchor.constraint(equalTo: viewController.view.safeAreaLayoutGuide.topAnchor),
    outerSquare.leftAnchor.constraint(equalTo: viewController.view.safeAreaLayoutGuide.leftAnchor),
    viewController.view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: outerSquare.bottomAnchor),
    viewController.view.safeAreaLayoutGuide.rightAnchor.constraint(equalTo: outerSquare.rightAnchor)
]

Supporting Right-to-Left languages

Although it might seem really obvious to just use leftAnchor and rightAnchor, you might want to think about using leadingAnchor and trailingAnchor instead. Doing so adds support for Right-to-Left languages to your views. This is mostly important for views like labels in which you want to make sure that they get flipped for Right-to-Left languages.

Performance of dynamically enabling and disabling constraints

Performance and Auto Layout constraints can be a topic on its own. Therefore, I’d like to refer you to this great talk of WWDC 2018: High Performance Auto Layout.

Valuable extensions to make your life easier

There’s a lot you can do to make it easier to work with constraints in code. By using those extensions you prevent yourself from writing a lot of boilerplate code.

Constraining a view to its superview

A common extension is to create a method to easily constraint view in its superview:

extension UIView {

    /// Returns a collection of constraints to anchor the bounds of the current view to the given view.
    ///
    /// - Parameter view: The view to anchor to.
    /// - Returns: The layout constraints needed for this constraint.
    func constraintsForAnchoringTo(boundsOf view: UIView) -> [NSLayoutConstraint] {
        return [
            topAnchor.constraint(equalTo: view.topAnchor),
            leadingAnchor.constraint(equalTo: view.leadingAnchor),
            view.bottomAnchor.constraint(equalTo: bottomAnchor),
            view.trailingAnchor.constraint(equalTo: trailingAnchor)
        ]
    }
}

Handling priorities

Once you have to set priorities to your constraints to prevent breaking constraints you might be happy with the following extension:

extension NSLayoutConstraint {
    
    /// Returns the constraint sender with the passed priority.
    ///
    /// - Parameter priority: The priority to be set.
    /// - Returns: The sended constraint adjusted with the new priority.
    func usingPriority(_ priority: UILayoutPriority) -> NSLayoutConstraint {
        self.priority = priority
        return self
    }
    
}

extension UILayoutPriority {
    
    /// Creates a priority which is almost required, but not 100%.
    static var almostRequired: UILayoutPriority {
        return UILayoutPriority(rawValue: 999)
    }
    
    /// Creates a priority which is not required at all.
    static var notRequired: UILayoutPriority {
        return UILayoutPriority(rawValue: 0)
    }
}

An Auto Layout Property Wrapper

Prevent yourself from constantly writing:

translatesAutoresizingMaskIntoConstraints = false

By making use of the following Property Wrapper:

@propertyWrapper
public struct UsesAutoLayout<T: UIView> {
    public var wrappedValue: T {
        didSet {
            wrappedValue.translatesAutoresizingMaskIntoConstraints = false
        }
    }

    public init(wrappedValue: T) {
        self.wrappedValue = wrappedValue
        wrappedValue.translatesAutoresizingMaskIntoConstraints = false
    }
}

final class MyViewController {
    @UsesAutoLayout
    var label = UILabel()
}

If you want to learn more about Property Wrappers, check out my blog post Property wrappers to remove boilerplate code in Swift.

Debugging Auto Layout constraints

[LayoutConstraints] Unable to simultaneously satisfy constraints.

This is probably a common message for you if you’ve been working with constraints for a while now. I wrote a blog post on User Interface debugging tools that can help you solve Auto Layout issues: UI Debugging by making use of third-party apps.

The need for an external dependency when writing constraints in code

There are a lot of external dependencies that say that they make it easier to write constraints. I hope that the above examples showed that it’s already quite easy to write constraints in code by making use of the default APIs. External frameworks introduce custom code patterns that might not be familiar to current and future colleagues.

Therefore, I would recommend you to make use of Layout Anchors and stick with Apple’s default APIs.

Conclusion

Writing your Auto Layout constraints in code should be a bit more easy for you after reading this post. It’s a great alternative to setting up constraints in the Interface Builder.

If you like to improve your Swift knowledge, even more, check out the Swift category page. Feel free to contact me or tweet to me on Twitter if you have any additional tips or feedback.

Thanks!

 

Stay updated with the latest in Swift

The 2nd largest newsletter in the Apple development community with 18,327 developers.


Featured SwiftLee Jobs

Find your next Swift career step at world-class companies with impressive apps by joining the SwiftLee Talent Collective. I'll match engineers in my collective with exciting app development companies. SwiftLee Jobs