Interface Builder makes it very easy to create user interfaces that are driven by Auto Layout. Adding, editing, and deleting constraints is a breeze in Interface Builder. But sometimes you need to work with Auto Layout in code.

Few developers look forward to working with constraints in code. The syntax is verbose and it can be difficult to understand which constraints to add without the visual cues of Interface Builder. Let's be honest, Interface Builder is great for building user interfaces

Setting Up the Project

In this tutorial, we are going to create a form with two text fields and a button. The form is 200 points wide and horizontally centered in its superview. This is the result we are after.

This is the result we are after.

Create a new project in Xcode based on the Single View Application template. Name the project AutoLayoutInCode, set Language to Swift, and Devices to Universal. Tell Xcode where you’d like to save the project and click Create.

Project Setup

Project Setup

Preparing the User Interface

Open ViewController.swift and declare a property for each of the user interface elements of the form, that is, a button and two text fields. Because the user interface elements are positioned and sized by the Auto Layout engine, the frame you pass to the initializer of UITextField is not important.

import UIKit

class ViewController: UIViewController {

    // MARK: - Properties

    let button = UIButton(type: .custom)
    let textFieldTop = UITextField(frame: .zero)
    let textFieldBottom = UITextField(frame: .zero)

}

In the view controller’s viewDidLoad() method, we configure the user interface elements and add them to the view of the view controller.

override func viewDidLoad() {
    super.viewDidLoad()

    // Configure Text Field Top
    textFieldTop.borderStyle = .roundedRect

    view.addSubview(textFieldTop)

    // Configure Text Field Bottom
    textFieldBottom.borderStyle = .roundedRect

    view.addSubview(textFieldBottom)

    // Configure Button
    button.setTitle("Submit", for: .normal)
    button.setTitleColor(.black, for: .normal)

    view.addSubview(button)
}

Because we haven’t added any constraints yet, the user interface doesn’t look great. It is time to add constraints for the text fields and the button.

Defining Constraints

Because I don’t want to clutter the viewDidLoad() method, we implement a few helper methods in which create the constraints for each of the user interface elements. Let us start with the top text field.

We create a new method, generateConstraintsTextFieldTop(), which returns an array of NSLayoutConstraint instances. An instance of the NSLayoutConstraint class represents an Auto Layout constraint.

private func generateConstraintsTextFieldTop() -> [NSLayoutConstraint] {

}

To position and size the text field, we need to create three constraints. Three? Shouldn't we create four constraints?

A text field already knows what its width and height should be. A text field has an intrinsic content size, which means that we don’t need to create explicit constraints for its width and height. But remember that we want the form to be 200 points wide, which means we need to define an explicit constraint to define the width of the text field.

The first constraint we create pins the text field to the top layout guide of the view controller. To create an instance of the NSLayoutConstraint class, we invoke a pretty length method as you can see below.

let constraintTop = NSLayoutConstraint(item: textFieldTop,
                                       attribute: .top,
                                       relatedBy: .equal,
                                       toItem: topLayoutGuide,
                                       attribute: .top,
                                       multiplier: 1.0,
                                       constant: 28.0)

This method accepts seven arguments. The NSLayoutConstraint class uses the values of the arguments to create the expression for the constraint. Remember that every constraint can be translated into an expression.

item1.attribute1 [relation] item2.attribute2 * multiplier + constant

With the above expression in mind, the arguments of the method make more sense.

  • item on the left side of the expression
  • constrained attribute of the left item
  • relation between the left and right side of the expression
  • item on the right side of the expression
  • constrained attribute of the right item
  • multiplier of the attribute of the right item
  • constant of the attribute of the right item

It makes more sense if we inspect the constraint that pins the top text field to the top layout guide of the view controller. The first argument is the top text field and the attribute, the second argument, is the top edge of the top text field.

The value of the third argument is the relation between the left and right expression, .equal in this example. The item of the right expression is the top layout guide of the view controller and the attribute is the top edge of the top layout guide.

The multiplier is 1.0 and the constant is 28.0. This means that the top edge of the top text field is positioned 28 points below the top edge of the top layout guide.

The remaining constraints should now be easier to understand. We center the top text field in its superview, the view of the view controller. This is the constraint that takes care of that.

let constraintX = NSLayoutConstraint(item: textFieldTop,
                                     attribute: .centerX,
                                     relatedBy: .equal,
                                     toItem: view,
                                     attribute: .centerX,
                                     multiplier: 1.0,
                                     constant: 0.0)

The left item of the expression is the top text field and the attribute is its horizontal center. The relation is identical to that of the previous constraint. The right item of the expression is the view of the view controller and the attribute is also its horizontal center. The multiplier is equal to 1.0 and the constant is equal to 0.0 because we want the horizontal centers of the items to align.

The third constraint defines the width of the top text field. This constraint may look a bit odd at first.

let constraintWidth = NSLayoutConstraint(item: textFieldTop,
                                         attribute: .width,
                                         relatedBy: .equal,
                                         toItem: nil,
                                         attribute: .notAnAttribute,
                                         multiplier: 1.0,
                                         constant: 200.0)

The left item of the expression is the top text field and we constrain its width. Because we constrain the size of the top text field, the expression for the constraint doesn’t include a second item. That is why the fourth argument is nil and the value of the fifth argument is equal to .notAnAttribute, a special value. The multiplier is equal to 1.0 and the constant is equal to 200.0, the desired width of the top text field.

We return the constraints as an array at the end of the generateConstraintsTextFieldTop() method.

private func generateConstraintsTextFieldTop() -> [NSLayoutConstraint] {
    let constraintTop = ...
    let constraintX = ...
    let constraintWidth = ...

    return [constraintTop, constraintX, constraintWidth]
}

Adding Constraints

Before we can see the result, we need to add the constraints. This is straightforward, but we need to mindful of a few details. A constraint should always be added to the view that contains both items of the constraint. We need to add the constraints of the top text field to its superview.

If we add the constraints to the top text field, the Auto Layout engine will complain at runtime. Instead, we add the constraints to the view of the view controller, the parent view of the top text field. We pass the array of constraints to the addConstraints(_:) method, a method of the UIView class.

view.addConstraints(generateConstraintsTextFieldTop())

We also need to make sure the constraints are added after adding the top text field as a subview to the view of the view controller. It makes no sense to define constraints for a view that isn’t in the view hierarchy. Right?

view.addSubview(textFieldTop)

view.addConstraints(generateConstraintsTextFieldTop())

There is one more detail we need to take care of. If you programmatically add a view to a view hierarchy, the autoresizing mask of the view is automatically translated into Auto Layout constraints. While this is fine in some situations, those constraints can conflict with the constraints we explicitly add to the view.

We need to tell the Auto Layout engine that it shouldn't generate constraints for the autoresizing mask of the view by setting its translatesAutoresizingMaskIntoConstraints property to false. This is what the viewDidLoad() method now looks like.

override func viewDidLoad() {
    super.viewDidLoad()

    // Configure Text Field Top
    textFieldTop.borderStyle = .roundedRect
    textFieldTop.translatesAutoresizingMaskIntoConstraints = false

    view.addSubview(textFieldTop)

    view.addConstraints(generateConstraintsTextFieldTop())

    ...
}

If you run the application, the top text field should be position below the status bar, horizontally centered in its superview.

The top text field is positioned below the status bar.

Exercise

You should now be able to create constraints for the bottom text field and the button. Take a moment to create the constraints for these user interface elements. You can find the solution below.

Solution

I have created a helper method to create the constraints for the bottom text field. This is what its implementation looks like.

private func generateConstraintsTextFieldBottom() -> [NSLayoutConstraint] {
    let constraintTop = NSLayoutConstraint(item: textFieldBottom, attribute: .top, relatedBy: .equal, toItem: textFieldTop, attribute: .bottom, multiplier: 1.0, constant: 8.0)
    let constraintLeading = NSLayoutConstraint(item: textFieldBottom, attribute: .leading, relatedBy: .equal, toItem: textFieldTop, attribute: .leading, multiplier: 1.0, constant: 0.0)
    let constraintTrailing = NSLayoutConstraint(item: textFieldBottom, attribute: .trailing, relatedBy: .equal, toItem: textFieldTop, attribute: .trailing, multiplier: 1.0, constant: 0.0)

    return [constraintTop, constraintLeading, constraintTrailing]
}

Notice that I don’t create a constraint to fix the width of the bottom text field. Instead, the leading and trailing edges of the bottom text field align with those of the top text field. This adds flexibility to the user interface. For example, if you later decide to increase the width of the form to 240 points, you only need to modify the constraint that constrains the width of the top text field.

The constraints for the button look similar, but notice that we don’t define a width for the button. The intrinsic content size of the button takes care of sizing the button. We only align its horizontal center with that of the top text field.

private func generateConstraintsButton() -> [NSLayoutConstraint] {
    let constraintTop = NSLayoutConstraint(item: button, attribute: .top, relatedBy: .equal, toItem: textFieldBottom, attribute: .bottom, multiplier: 1.0, constant: 8.0)
    let constraintX = NSLayoutConstraint(item: button, attribute: .centerX, relatedBy: .equal, toItem: textFieldTop, attribute: .centerX, multiplier: 1.0, constant: 0.0)

    return [constraintTop, constraintX]
}

This is what the viewDidLoad() method looks like.

override func viewDidLoad() {
    super.viewDidLoad()

    // Configure Text Field Top
    textFieldTop.borderStyle = .roundedRect
    textFieldTop.translatesAutoresizingMaskIntoConstraints = false

    view.addSubview(textFieldTop)

    view.addConstraints(generateConstraintsTextFieldTop())

    // Configure Text Field Bottom
    textFieldBottom.borderStyle = .roundedRect
    textFieldBottom.translatesAutoresizingMaskIntoConstraints = false

    view.addSubview(textFieldBottom)

    view.addConstraints(generateConstraintsTextFieldBottom())

    // Configure Button
    button.setTitle("Submit", for: .normal)
    button.setTitleColor(.black, for: .normal)
    button.translatesAutoresizingMaskIntoConstraints = false

    view.addSubview(button)

    view.addConstraints(generateConstraintsButton())
}

And this is what the completed user interface looks like.

This is what the completed user interface looks like.

What's Next

Working with Auto Layout constraints in code is no rocket science, but it is more verbose and tricker to get right than building a user interface in Interface Builder. At times, however, you have no other option than to create constraints in code.

You have several other options to create constraints in code. You can use Apple's Visual Format Language and you can also leverage layout anchors.

Because working with constraints in code can be tedious, many libraries have emerged over the years that make working with Auto Layout constraints easier and more intuitive.