Batch Updating of ConstraintsDec 12, 2016 · 4 minute read · Comments
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
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.
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:
- WWDC 2015 Session 218 - Mysteries of Auto Layout, Part 1
- WWDC 2015 Session 219 - Mysteries of Auto Layout, Part 2
For details on the Swift compiler hang when building arrays of constraints: