Implementing iOS 9 Auto Layout Constraints in Code

From Techotopia
Jump to: navigation, search
PreviousTable of ContentsNext
An iOS 9 Auto Layout ExampleImplementing Cross-Hierarchy Auto Layout Constraints in iOS 9


Purchase the fully updated iOS 10 / Swift 3 / Xcode 8 edition of this book in eBook ($19.99) or Print ($45.99) format.
iOS 10 App Development Essentials Print and eBook (ePub/PDF/Kindle) edition contains over 100 chapters. Learn more...

Buy eBook Buy Print Preview Book


In addition to using Interface Builder, it is also possible to create Auto Layout constraints directly within the code of an application. 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 cross view hierarchies being a prime example. Interface Builder is also of limited use when user interfaces are created dynamically at run time.

Given these facts, an understanding of how to create Auto Layout constraints in code is an important skill, and is the focus of this chapter.




Creating Constraints in Code

Implementing constraints in code is a two-step process which involves first creating the constraint, and then adding the constraint to a view.

In order 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 calling the constraintWithItem method and passing through a set of arguments for the constraint.

When considering this syntax, it is helpful to recall the way in which constraints can be represented using linear equations (as outlined in An Introduction to Auto Layout in iOS 9) because the elements of the equation 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 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: NSLayoutAttribute.Bottom,
                relatedBy: NSLayoutRelation.Equal,
                toItem: view2,
                attribute: NSLayoutAttribute.Bottom,
                multiplier: 1.0,
                constant: -20)

As we can see, the arguments to the method exactly match those of the equation (with the exception of 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 5 times the width of a Label view named myLabel:

var myConstraint =
            NSLayoutConstraint(item: myButton,
                attribute: NSLayoutAttribute.Width,
                relatedBy: NSLayoutRelation.Equal,
                toItem: myLabel,
                attribute: NSLayoutAttribute.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: NSLayoutAttribute.Width,
                relatedBy: NSLayoutRelation.GreaterThanOrEqual,
                toItem: nil,
                attribute: NSLayoutAttribute.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 in order 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 the question of how to decide which view the constraint should be added to.

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 the purposes of an example, the view hierarchy illustrated in Figure 21-1.


The view hierarchy for an iOS 9 Auto Layout code example

Figure 21-1


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

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

var myConstraint =
            NSLayoutConstraint(item: myButton,
                attribute: NSLayoutAttribute.Width,
                relatedBy: NSLayoutRelation.Equal,
                toItem: myLabel,
                attribute: NSLayoutAttribute.Width,
                multiplier: 5.0,
                constant: 0)

self.view.addConstraint(myConstraint)

Purchase the fully updated iOS 10 / Swift 3 / Xcode 8 edition of this book in eBook ($19.99) or Print ($45.99) format.
iOS 10 App Development Essentials Print and eBook (ePub/PDF/Kindle) edition contains over 100 chapters. Learn more...

Buy eBook Buy Print Preview Book


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 application code. It is essential, therefore, that translation be turned off for views to which constraints are to be added in 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)

An Example Application

Create a new Xcode project using the Single View Application template. Select Universal from the Devices menu and Swift from the language menu, entering AutoLayoutCode as the product name.

Creating the Views

For the purpose of this example, the code to create the views and constraints will be added to the viewDidLoad method of the AutoLayoutCode view controller. Select the ViewController.swift file, locate this method and modify it to create a button and a label and add them to the main view:

override func viewDidLoad() {
    super.viewDidLoad()

    let superview = self.view

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

    let myButton = UIButton()

    myButton.setTitle("My Button", forState: UIControlState.Normal)
    myButton.backgroundColor = UIColor.blueColor()
    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 viewDidLoad method needs to be modified as follows:

override func viewDidLoad() {
    super.viewDidLoad()

    let superview = self.view

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

    let myButton = UIButton()

    myButton.setTitle("My Button", forState: UIControlState.Normal)
    myButton.backgroundColor = UIColor.blueColor()
    myButton.translatesAutoresizingMaskIntoConstraints = false

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

    var myConstraint =
            NSLayoutConstraint(item: myLabel,
                attribute: NSLayoutAttribute.CenterY,
                relatedBy: NSLayoutRelation.Equal,
                toItem: superview,
                attribute: NSLayoutAttribute.CenterY,
                multiplier: 1.0,
                constant: 0)

    superview.addConstraint(myConstraint)

    myConstraint =
        NSLayoutConstraint(item: myLabel,
            attribute: NSLayoutAttribute.CenterX,
            relatedBy: NSLayoutRelation.Equal,
            toItem: superview,
            attribute: NSLayoutAttribute.CenterX,
            multiplier: 1.0,
            constant: 0)

    superview.addConstraint(myConstraint)

    myConstraint =
        NSLayoutConstraint(item: myButton,
            attribute: NSLayoutAttribute.Trailing,
            relatedBy: NSLayoutRelation.Equal,
            toItem: myLabel,
            attribute: NSLayoutAttribute.Leading,
            multiplier: 1.0,
            constant: -10)

    superview.addConstraint(myConstraint)

    myConstraint =
        NSLayoutConstraint(item: myButton,
            attribute: NSLayoutAttribute.Baseline,
            relatedBy: NSLayoutRelation.Equal,
            toItem: myLabel,
            attribute: NSLayoutAttribute.Baseline,
            multiplier: 1.0,
            constant: 0)

    superview.addConstraint(myConstraint)
}

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


Ios 8 auto layout code running.png

Figure 21-2


Removing Constraints

Whilst it has not been necessary to do so in this example, it is important to be aware that it is also possible to remove constraints from a view. 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 9.

Summary

Whilst 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 in situations where specific layout behavior cannot be achieved using Interface Builder (a prime example of this 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.


Purchase the fully updated iOS 10 / Swift 3 / Xcode 8 edition of this book in eBook ($19.99) or Print ($45.99) format.
iOS 10 App Development Essentials Print and eBook (ePub/PDF/Kindle) edition contains over 100 chapters. Learn more...

Buy eBook Buy Print Preview Book



PreviousTable of ContentsNext
An iOS 9 Auto Layout ExampleImplementing Cross-Hierarchy Auto Layout Constraints in iOS 9