Batch Updating of Constraints

Since Apple introduced Auto Layout way back in iOS 6 the API has allowed you to add constraints one at a time or as a batch. Most of the code samples on this blog have tended to add constraints one at a time for ease of explanation at the possible expense of run-time efficiency. In this post I want to be sure to show the alternative, mention a Swift bug that might bite you and what is perhaps a more pragmatic reason to prefer the batch approach.

Creating Constraints - A Recap

A quick recap to compare the API for creating a single constraint to creating multiple constraints:

Activate a single constraint

Since iOS 8 the preferred way to add and activate a single constraint is to set the isActive property on the constraint to true. From the Apple documentation for UIView:

When developing for iOS 8.0 or later, set the constraint’s isActive property to true instead of calling the addConstraint(_:) method directly. The isActive property automatically adds and removes the constraint from the correct view.

For example to create a vertical spacing between two labels using layout anchors (iOS 9):

// Swift
label2.topAnchor.constraint(equalTo: label1.bottomAnchor,
                 constant: 8.0).isActive = true

// Objective-C
[label2.topAnchor constraintEqualToAnchor:label1.bottomAnchor
                  constant:8.0].active = YES;

Activate a batch of constraints

If you have a set of constraints to create you can activate them in one batch using the NSLayoutConstraint class method. The activate method takes an array of constraints and if your target is iOS 8 or later is preferred over calling addConstraints on the view:

When developing for iOS 8.0 or later, use the NSLayoutConstraint class’s activate(:) method instead of calling the addConstraints(:) method directly. The activate(_:) method automatically adds the constraints to the correct views.

To activate a batch of constraints:

// Swift
NSLayoutConstraint.activate([
  label1.centerXAnchor.constraint(equalTo: view.centerXAnchor),
  label2.centerXAnchor.constraint(equalTo: view.centerXAnchor),
  label3.centerXAnchor.constraint(equalTo: view.centerXAnchor),
  // ... more constraints ...
])

// Objective-C
[NSLayoutConstraint activateConstraints:@[
  [label1.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
  [label2.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
  [label3.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
  // ... more constraints ...
 ]
];

Swift Compiler Hangs on Large Arrays

I should mention at this point if you are using Swift to create an array of NSLayoutConstraint items you may hit a bug that causes a complier hang. The bug still exists at time of writing (Swift 3 and Xcode 8.1). As a workaround create the array and then append and activate the constraints:

var constraints = [NSLayoutConstraint]()
constraints.append(NSLayoutConstraint(...))
...
NSLayoutConstraint.activate(constraints)

Annoying and hopefully fixed soon.

Which Way Is Best?

So does it matter which of the two methods you use? Should you activate the constraints in a single batch or one-by-one? Here is what the Apple documentation says about the activate class method (my emphasis):

This convenience method provides an easy way to activate a set of constraints with one call. The effect of this method is the same as setting the active property of each constraint to YES. Typically, using this method is more efficient than activating each constraint individually.

So typically it is faster to batch up the constraints and activate them together. In practise I am not sure how much a difference it typically makes compared to other factors such as the view complexity but there seems to be no good reason not to batch up constraints when possible.

There is a more pragmatic reason why I prefer the batch oriented approach of using activateConstraints on an array of constraints (and why I find the Swift bug so annoying). When I activate constraints one at a time it is easy to forget to set isActive and instead write this:

label2.topAnchor.constraint(equalTo: label1.bottomAnchor,
                 constant: 8.0)

It looks good but the constraint is never added to the view and you waste time scratching your head while you debug an incorrect layout. Compare that to the class method where you create the array of constraints inline so you can never forget to activate them:

NSLayoutConstraint.activate([
  label2.topAnchor.constraint(equalTo: label1.bottomAnchor,
                 constant: 8.0),
  ... other constraints ...
])

So run-time efficiency and less debug time hunting for inactive constraints.

Further Reading

For details on the move in iOS 8 from add/remove to activate/deactivate of constraints and the efficiency of bulk updating see the two Auto Layout sessions from WWDC 2015:

For details on the Swift compiler hang when building arrays of constraints:

Never miss a post!

iOS Size Classes Cheat Sheet

Subscribe and get my free iOS Size Classes Cheat Sheet

Unsubscribe at any time.
No time to watch WWDC videos?

Sign up to get my iOS posts direct to your inbox and I will send you a free PDF of my iOS Size Classes Cheat Sheet.

Unsubscribe at any time.
Archives Categories
comments powered by Disqus