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.
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:
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.
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.
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
- 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.
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.
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:
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).
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.