Implementing iOS 17 Auto Layout Constraints in Code

In addition to using Interface Builder, it is also possible to create Auto Layout constraints directly within the code of an app. These approaches, however, are not necessarily mutually exclusive. There are, for example, situations where a layout will be constructed using a combination of Interface Builder and manual coding. Furthermore, some types of constraint cannot yet be implemented in Interface Builder, constraints that crossview hierarchies being a prime example. Finally, interface Builder is also of limited use when user interfaces are created dynamically at run time.

Given these facts, understanding how to create Auto Layout constraints in code is an important skill. In this chapter, we will explore two ways to create constraints in code. We will begin by exploring constraints using the NSLayoutConstraint class and then look at a more straightforward solution involving the NSLayoutAnchor class.

Creating Constraints Using NSLayoutConstraint

Implementing constraints using the NSLayoutConstraint class is a two-step process that involves creating the constraint and then adding the constraint to a view.

To create a constraint, an instance of the NSLayoutConstraint class must be created and initialized with the appropriate settings for the Auto Layout behavior it is to implement. This is achieved by initializing an NSLayoutConstraint instance, passing through a set of arguments for the constraint.

When considering this syntax, it is helpful to recall how constraints can be represented using linear equations (as outlined in An Introduction to Auto Layout in iOS 17) because the equation elements match the arguments used to create an NSLayoutConstraint instance.

 

You are reading a sample chapter from Building iOS 17 Apps using Xcode Storyboards.

Buy the full book now in eBook or Print format.

The full book contains 96 chapters and 760 pages of in-depth information.

Learn more.

Preview  Buy eBook  Buy Print

 

Consider, for example, the following constraint expressed as an equation:

view1.bottom = view2.bottom – 20

The objective of this constraint is to position view1 so that its bottom edge is positioned at a distance of 20 points above the bottom edge of view2. This same equation can be represented in code as follows:

var myConstraint =
            NSLayoutConstraint(item: view1,
                attribute: NSLayoutConstraint.Attribute.bottom,
                relatedBy: NSLayoutRelation.equal,
                toItem: view2,
                attribute: NSLayoutConstraint.Attribute.bottom,
                multiplier: 1.0,
                constant: -20)

As we can see, the arguments to the method match those of the equation (except for the multiplier, which is absent from the equation and therefore equates to 1 in the method call).

The following equation sets the width of a Button view named myButton to be five times the width of a Label view named myLabel:

var myConstraint =
            NSLayoutConstraint(item: myButton,
                attribute: NSLayoutConstraint.Attribute.width,
                relatedBy: NSLayoutRelation.equal,
                toItem: myLabel,
                attribute: NSLayoutConstraint.Attribute.width,
                multiplier: 5.0,
                constant: 0)

So far, the examples shown in this chapter have been equality-based constraints, and, as such, the relatedBy: argument has been set to NSLayoutRelation.Equal. The following equation uses a greater than or equal to operator:

 

You are reading a sample chapter from Building iOS 17 Apps using Xcode Storyboards.

Buy the full book now in eBook or Print format.

The full book contains 96 chapters and 760 pages of in-depth information.

Learn more.

Preview  Buy eBook  Buy Print

 

myButton.width >= 200

Translated into code, this reads as follows:

var myConstraint =
            NSLayoutConstraint(item: myButton,
                attribute: NSLayoutConstraint.Attribute.width,
                relatedBy: NSLayoutRelation.greaterThanOrEqual,
                toItem: nil,
                attribute: NSLayoutConstraint.Attribute.width,
                multiplier: 1.0,
                constant: 200)

Note that since this constraint is not related to another view, the toItem: argument is set to nil.

Adding a Constraint to a View

Once a constraint has been created, it needs to be assigned to a view to become active. This is achieved by passing it through as an argument to the addConstraint method of the view instance to which it is being added. In the case of multiple constraints, each is added by a separate call to the addConstraint method. This leads to deciding the view to which the constraint should be added.

In the case of a constraint that references a single view, the constraint must be added to the immediate parent of the view. When a constraint references two views, the constraint must be applied to the closest ancestor of the two views. Consider, for example, the view hierarchy illustrated in Figure 20-1.

Figure 20-1

A constraint referencing only Label A should be added to the immediate parent, View B. On the other hand, a constraint referencing Button B and Label B must be added to the nearest common ancestor, which in this case is View C. Finally, a constraint referencing Button A and Button B must, once again, be added to the nearest common ancestor which equates to View A.

 

You are reading a sample chapter from Building iOS 17 Apps using Xcode Storyboards.

Buy the full book now in eBook or Print format.

The full book contains 96 chapters and 760 pages of in-depth information.

Learn more.

Preview  Buy eBook  Buy Print

 

For example, the following code excerpt creates a new constraint and adds it to a view:

var myConstraint =
            NSLayoutConstraint(item: myButton,
                attribute: NSLayoutConstraint.Attribute.width,
                relatedBy: NSLayoutRelation.equal,
                toItem: myLabel,
                multiplier: 5.0,
                constant: 0)

self.view.addConstraint(myConstraint)

Turning off Auto Resizing Translation

When adding views to a layout in code, the toolkit will, by default, attempt to convert the autosizing mask for that view to Auto Layout constraints. Unfortunately, those auto-generated constraints will conflict with any constraints added within the app code. It is essential, therefore, that translation is turned off for views to which constraints are to be added in the code. This is achieved by setting the setTranslatesAutoresizingMaskIntoConstraints property of the target view to false. For example, the following code creates a new Button view, turns off translation, and then adds it to the parent view:

let myButton = UIButton()

myButton.setTitle("My Button", forState: UIControlState.normal)
myButton.translatesAutoresizingMaskIntoConstraints = false

self.view.addSubview(myButton)

Creating Constraints Using NSLayoutAnchor

An alternative to using the NSLayoutConstraint class is configuring constraints using layout anchors. Layout Anchors are created using the NSLayoutAnchor class, which creates NSLayoutConstraint instances for us, resulting in more concise code that is easier to write and understand.

UIView and all of its subclasses (UIButton, UILabel, etc.) contain a set of anchor properties that can be used to create constraints relative to anchor properties on other views in a layout. These anchor properties are instances of the NSLayoutAnchor class. The complete set of anchor properties is as follows:

Horizontal Anchors

 

You are reading a sample chapter from Building iOS 17 Apps using Xcode Storyboards.

Buy the full book now in eBook or Print format.

The full book contains 96 chapters and 760 pages of in-depth information.

Learn more.

Preview  Buy eBook  Buy Print

 

  • centerXAnchor
  • leadingAnchor
  • trailingAnchor
  • leftAnchor
  • rightAnchor

Vertical Anchors

  • centerYAnchor
  • bottomAnchor
  • topAnchor
  • firstBaselineAnchor
  • lastBaselineAnchor

Size Anchors

  • heightAnchor
  • widthAnchor

A constraint is created by calling the constraint() method on one of the above properties of a view and passing it details of the view and anchor to which it is to be constrained. Suppose, for example, that we want to center a UILabel horizontally and vertically within a parent view. The required code would read as follows:

myLabel.centerXAnchor.constraint(equalTo: myView.centerXAnchor).isActive=true
myLabel.centerYAnchor.constraint(equalTo: myView.centerYAnchor).isActive=true

Using this approach, we can establish constraints between different anchor property types and include constant values. The following code, for example, constrains the bottom of a view named myLabel to the top of a view named myButton using an offset of 100:

myLabel.bottomAnchor.constraint(equalTo: myButton.topAnchor, 
                                        constant: 100).isActive=true

Not only do layout anchors require considerably less code than NSLayoutConstraint, but they also make the code easier to understand.

 

You are reading a sample chapter from Building iOS 17 Apps using Xcode Storyboards.

Buy the full book now in eBook or Print format.

The full book contains 96 chapters and 760 pages of in-depth information.

Learn more.

Preview  Buy eBook  Buy Print

 

An Example App

Create a new Xcode project using the iOS App template, enter AutoLayoutCode as the product name, and set the Interface and Language menus to Storyboard and Swift, respectively.

Creating the Views

For this example, the code to create the views and constraints will be implemented in a new method named createLayout which will, in turn, be called from the viewDidLoad method of the AutoLayoutCode view controller. Select the ViewController.swift file and add this code to create a button and a label and add them to the main view:

override func viewDidLoad() {
    super.viewDidLoad()
    createLayout()
}

func createLayout() {
    let superview = self.view
    
    let myLabel = UILabel()
    myLabel.translatesAutoresizingMaskIntoConstraints = false
    myLabel.text = "My Label"
    
    let myButton = UIButton()
    
    myButton.setTitle("My Button", for: UIControl.State.normal)
    myButton.backgroundColor = UIColor.blue
    myButton.translatesAutoresizingMaskIntoConstraints = false
    
    superview?.addSubview(myLabel)
    superview?.addSubview(myButton)
}

Creating and Adding the Constraints

Constraints will be added to position the label in the horizontal and vertical center of the superview. The button will then be constrained to be positioned to the left of the label with the baselines of both views aligned. To achieve this layout, the createLayout method needs to be modified as follows:

func createLayout() {
    
    let superview = self.view

    let myLabel = UILabel()
    myLabel.translatesAutoresizingMaskIntoConstraints = false
    myLabel.text = "My Label"

    let myButton = UIButton()

    myButton.setTitle("My Button", for: UIControlState.normal)
    myButton.backgroundColor = UIColor.blue
    myButton.translatesAutoresizingMaskIntoConstraints = false

    superview?.addSubview(myLabel)
    superview?.addSubview(myButton)

    var myConstraint =
        NSLayoutConstraint(item: myLabel,
               attribute: NSLayoutConstraint.Attribute.centerY,
               relatedBy: NSLayoutConstraint.Relation.equal,
               toItem: superview,
               attribute: NSLayoutConstraint.Attribute.centerY,
               multiplier: 1.0,
               constant: 0)
    
    superview?.addConstraint(myConstraint)
    
    myConstraint =
        NSLayoutConstraint(item: myLabel,
                attribute: NSLayoutConstraint.Attribute.centerX,
                relatedBy: NSLayoutConstraint.Relation.equal,
                toItem: superview,
                attribute: NSLayoutConstraint.Attribute.centerX,
                multiplier: 1.0,
                constant: 0)
    
    superview?.addConstraint(myConstraint)
    
    myConstraint =
        NSLayoutConstraint(item: myButton,
               attribute: NSLayoutConstraint.Attribute.trailing,
               relatedBy: NSLayoutConstraint.Relation.equal,
               toItem: myLabel,
               attribute: NSLayoutConstraint.Attribute.leading,
               multiplier: 1.0,
               constant: -10)
    
    superview?.addConstraint(myConstraint)
    
    myConstraint =
        NSLayoutConstraint(item: myButton,
              attribute: NSLayoutConstraint.Attribute.lastBaseline,
              relatedBy: NSLayoutConstraint.Relation.equal,
              toItem: myLabel,
              attribute: NSLayoutConstraint.Attribute.lastBaseline,
              multiplier: 1.0,
              constant: 0)
    
    superview?.addConstraint(myConstraint)
}

When the app is compiled and run, the layout of the two views should match that illustrated in Figure 20-2.

Figure 20-2

Using Layout Anchors

Now that we have implemented constraints using the NSLayoutConstraint class, we can make a comparison by replacing this code with the equivalent layout anchor code. Remaining in the ViewController.swift file, remove or comment out the previously added constraint code and replace it with the following:

 

You are reading a sample chapter from Building iOS 17 Apps using Xcode Storyboards.

Buy the full book now in eBook or Print format.

The full book contains 96 chapters and 760 pages of in-depth information.

Learn more.

Preview  Buy eBook  Buy Print

 

myLabel.centerXAnchor.constraint(equalTo: superview!.centerXAnchor).isActive=true
myLabel.centerYAnchor.constraint(equalTo: superview!.centerYAnchor).isActive=true
myButton.trailingAnchor.constraint(equalTo: myLabel.leadingAnchor, constant: -10)
                                                         .isActive=true
myButton.lastBaselineAnchor.constraint(equalTo: myLabel.lastBaselineAnchor)
                                                         .isActive=true

Rerun app to confirm the layout anchor code creates the same layout as before.

Removing Constraints

While it has not been necessary in this example, it is important to be aware that removing constraints from a view is possible. This can be achieved simply by calling the removeConstraint method of the view to which the constraint was added, passing through as an argument the NSLayoutConstraint object matching the constraint to be removed:

self.myview.removeConstraint(myconstraint)

It is also worth knowing that constraints initially created in Interface Builder can be connected to outlet properties, thereby allowing them to be referenced in code. The steps involved in creating an outlet for a constraint are covered in more detail in “Implementing Cross-Hierarchy Auto Layout Constraints in iOS 17”.

Layout anchor constraints can be enabled and disabled by saving the constraint to a variable and changing the isActive property as follows:

var constraint = myLabel.centerXAnchor.constraint(
                                    equalTo: superview!.centerXAnchor)

constraint.isActive=true   // Enable the constraint
constraint.isActive=false  // Disable the constraint

Summary

While Interface Builder is the recommended method for implementing Auto Layout constraints, there are still situations where it may be necessary to implement constraints in code. This is typically necessary when dynamically creating user interfaces or when specific layout behavior cannot be achieved using Interface Builder (a prime example of these being constraints that cross-view hierarchies, as outlined in the next chapter).

 

You are reading a sample chapter from Building iOS 17 Apps using Xcode Storyboards.

Buy the full book now in eBook or Print format.

The full book contains 96 chapters and 760 pages of in-depth information.

Learn more.

Preview  Buy eBook  Buy Print

 

Constraints are created in code by instantiating instances of the NSLayoutConstraint class, configuring those instances with the appropriate constraint settings, and then adding the constraints to the appropriate views in the user interface.


Categories