Why Calling Setneedsupdateconstraints Isn't Needed for Constraint Changes or Animations

Why calling setNeedsUpdateConstraints isn't needed for constraint changes or animations?

This is a common misunderstanding among iOS developers.

Here's one of my "golden rules" for Auto Layout:

Don't bother about "updating constraints".

You never need to call any of these methods:

  • setNeedsUpdateConstraints()
  • updateConstraintsIfNeeded()
  • updateConstraints()
  • updateViewConstraints()

except for the very rare case that you have a tremendously complex layout which slows down your app (or you deliberately choose to implement layout changes in an atypical way).

The Preferred Way to Change Your Layout

Normally, when you want to change your layout, you would activate / deactivate or change layout constraints directly after a button tap or whichever event triggered the change, e.g. in a button's action method:

@IBAction func toggleLayoutButtonTapped(_ button: UIButton) {
toggleLayout()
}

func toggleLayout() {
isCenteredLayout = !isCenteredLayout

if isCenteredLayout {
centerXConstraint.isActive = true
} else {
centerXConstraint.isActive = false
}
}

As Apple puts it in their Auto Layout Guide:

It is almost always cleaner and easier to update a constraint immediately after the affecting change has occurred. Deferring these changes to a later method makes the code more complex and harder to understand.

You can of course also wrap this constraint change in an animation: You first perform the constraint change and then animate the changes by calling layoutIfNeeded() in the animation closure:

@IBAction func toggleLayoutButtonTapped(_ button: UIButton) {
// 1. Perform constraint changes:
toggleLayout()
// 2. Animate the changes:
UIView.animate(withDuration: 1.8, animations: {
view.layoutIfNeeded()
}
}

Whenever you change a constraint, the system automatically schedules a deferred layout pass, which means that the system will recompute the layout in the near future. No need to call setNeedsUpdateConstraints() because you just did update (change) the constraint yourself! What needs to be updated is the layout i.e. the frames of all your views, not any other constraint.

The Principle of Invalidation

As previously stated, the iOS layout system usually doesn't react immediately to constraint changes but only schedules a deferred layout pass. That's for performance reasons. Think of it like this:

When you go shopping groceries, you put an item in your cart but you don't pay it immediately. Instead, you put other items in your cart until you feel like you got everything you need. Only then you proceed to the cashier and pay all your groceries at once. It's way more efficient.

Due to this deferred layout pass there is a special mechanism needed to handle layout changes. I call it The Principle of Invalidation. It's a 2-step mechanism:

  1. You mark something as invalid.
  2. If something is invalid, you perform some action to make it valid again.

In terms of the layout engine this corresponds to:

  1. setNeedsLayout()
  2. layoutIfNeeded()

and

  1. setNeedsUpdateConstraints()
  2. updateConstraintsIfNeeded()

The first pair of methods will result in an immediate (not deferred) layout pass: First you invalidate the layout and then you recompute the layout immediately if it's invalid (which it is, of course).

Usually you don't bother if the layout pass will happen now or a couple of milliseconds later so you normally only call setNeedsLayout() to invalidate the layout and then wait for the deferred layout pass. This gives you the opportunity to perform other changes to your constraints and then update the layout slightly later but all at once (→ shopping cart).

You only need to call layoutIfNeeded() when you need the layout to be recomputed right now. That might be the case when you need to perform some other calculations based on the resulting frames of your new layout.

The second pair of methods will result in an immediate call of updateConstraints() (on a view or updateViewConstraints() on a view controller). But that's something you normally shouldn't do.

Changing Your Layout in a Batch

Only when your layout is really slow and your UI feels laggy due to your layout changes you can choose a different approach than the one stated above: Rather than updating a constraint directly in response to a button tap you just make a "note" of what you want to change and another "note" that your constraints need to be updated.

@IBAction func toggleLayoutButtonTapped(_ button: UIButton) {
// 1. Make a note how you want your layout to change:
isCenteredLayout = !isCenteredLayout
// 2. Make a note that your constraints need to be updated (invalidate constraints):
setNeedsUpdateConstraints()
}

This schedules a deferred layout pass and ensures that updateConstraints() / updateViewConstraints() will be called during the layout pass. So you may now even perform other changes and call setNeedsUpdateConstraints() a thousand times – your constraints will still only be updated once during the next layout pass.

Now you override updateConstraints() / updateViewConstraints() and perform the necessary constraint changes based on your current layout state (i.e. what you have "noted" above in "1."):

override func updateConstraints() {
if isCenteredLayout {
centerXConstraint.isActive = true
} else {
centerXConstraint.isActive = false
}

super.updateConstraints()
}

Again, this is only your last resort if the layout is really slow and you're dealing will hundreds or thousands of constraints. I have never needed to use updateConstraints() in any of my projects, yet.

I hope this make things a little clearer.

Additional resources:

  • Auto Layout – From Leading to Trailing: my talk from UIKonf 2017, topics:

    • "The Layout Pass" and
    • "Updating Constraints"
  • The Auto Layout Comprehendium™: scroll down to section "Updating Constraints", maintained by me
  • The Auto Layout Guide by Apple: sections

    • "Changing Constraints"
    • "The Deferred Layout Pass"

Animating Constraint Changes in -(void)updateConstraints

You need to call [self updateConstraintsIfNeeded]. So your code would look like this...

[self updateConstraintsIfNeeded];
[UIView animateWithDuration:0.25 animations:^{
[self layoutIfNeeded];
}];

setNeedsLayout vs. setNeedsUpdateConstraints and layoutIfNeeded vs updateConstraintsIfNeeded

Your conclusions are right. The basic scheme is:

  • setNeedsUpdateConstraints makes sure a future call to updateConstraintsIfNeeded calls updateConstraints.
  • setNeedsLayout makes sure a future call to layoutIfNeeded calls layoutSubviews.

When layoutSubviews is called, it also calls updateConstraintsIfNeeded, so calling it manually is rarely needed in my experience. In fact, I have never called it except when debugging layouts.

Updating constraints using setNeedsUpdateConstraints is pretty rare too, objc.io–a must read about autolayouts–says:

If something changes later on that invalidates one of your constraints, you should remove the constraint immediately and call setNeedsUpdateConstraints. In fact, that’s the only case where you should have to trigger a constraint update pass.

In addition, in my experience, I have never had to invalidate constraints, and not set the setNeedsLayout in the next line of the code, because new constraints pretty much are asking for a new layout.

The rules of thumb are:

  • If you manipulated constraints directly, call setNeedsLayout.
  • If you changed some conditions (like offsets or smth) which would change constraints in your overridden updateConstraints method (a recommended way to change constraints, btw), call setNeedsUpdateConstraints, and most of the time, setNeedsLayout after that.
  • If you need any of the actions above to have immediate effect—e.g. when your need to learn new frame height after a layout pass—append it with a layoutIfNeeded.

Also, in your animation code, I believe setNeedsUpdateConstraints is unneeded, since constraints are updated before the animation manually, and the animation only re-lays-out the view based on differences between the old and new ones.

What is the difference between all these Auto Layout update methods? Are all necessary?


Layout

Suppose you encapsulate your view logic in a UIView subclass, and call it SomeView. This means that SomeView should know how to layout itself, that is, how to position some other views inside it (you can also create a view that draws itself without using any subviews, but that's beyond the needs of an average developer).

This layout is done by [SomeView layoutSubviews]. You have an option of overriding it:

subview.frame = CGRectMake(100 + shiftX, 50 + shiftY, 250 + shiftX - paddingRight, ...
// Oh no, I think I didn't do it right.

but you rarely need to do so. In the dark ages of Cocoa Touch, this manual layout was widespread, but now I'd say 99% of layouts can be covered with Auto Layout.

The system needs to know when it should call [UIView layoutSubviews]. It's obviously done the first time you need to draw a view, but it may be also called whenever the superview frame changes. Here's a detailed explanation.

So the system often calls [view layoutIfNeeded]. You can also call it at any time, but this will have an effect only if there is some event that has called [view setNeedsLayout] or if you have called it manually, as in this case.

Constraints

The Auto Layout (capitalized this way in the documentation) is called like that because you're leaving [SomeView layoutSubviews] as it is inherited from UIView and
describe the position of your subviews instead in terms of constraints.

When using Auto Layout, system will perform calls to [view updateConstraintsIfNeeded] at each layout pass. However, only if the flag [view setNeedsUpdateConstraints]; is set, the method calls into -updateConstraints (which does the real job).

If you don't use Auto Layout, those methods are not relevant.

You can implement it like in this example.

Your example

It's rarely necessary to call -layoutIfNeeded and -updateConstraintsIfNeeded directly, because UI engine will do that automatically at each layout pass. However, in this case the author has chosen to call them immediately; this is because the resulting height is needed right now, not at some point in the future.

This method of updating the cell's height seems right. Note that cell could be a newly created cell and thus not added into view hierarchy yet; this does not impact its ability to layout itself.

Conclusion

In your custom view go with the following options, starting from the most 'universal' to most 'customized':

  1. Create constraints during view creation (manually or in the IB)
  2. If you need to change the constraints later, override -updateConstraints.
  3. If have a complex layout that cannot be described by above means, override -layoutSubviews.

In the code that changes something that could make your view's constraints change, call

[view setNeedsUpdateConstraints];

If you need results immediately, call also

[view updateConstraintsIfNeeded]; 

If the code changes view's frame use

[view setNeedsLayout]; 

and finally if you want the results immediately, call

[view layoutIfNeeded];

This is why all four calls are required in this case.

Additional materials

Take a look at detailed explanation in the article Advanced Auto Layout Toolbox, objc.io issue #3

How do I animate constraint changes?

Two important notes:

  1. You need to call layoutIfNeeded within the animation block. Apple actually recommends you call it once before the animation block to ensure that all pending layout operations have been completed

  2. You need to call it specifically on the parent view (e.g. self.view), not the child view that has the constraints attached to it. Doing so will update all constrained views, including animating other views that might be constrained to the view that you changed the constraint of (e.g. View B is attached to the bottom of View A and you just changed View A's top offset and you want View B to animate with it)

Try this:

Objective-C

- (void)moveBannerOffScreen {
[self.view layoutIfNeeded];

[UIView animateWithDuration:5
animations:^{
self._addBannerDistanceFromBottomConstraint.constant = -32;
[self.view layoutIfNeeded]; // Called on parent view
}];
bannerIsVisible = FALSE;
}

- (void)moveBannerOnScreen {
[self.view layoutIfNeeded];

[UIView animateWithDuration:5
animations:^{
self._addBannerDistanceFromBottomConstraint.constant = 0;
[self.view layoutIfNeeded]; // Called on parent view
}];
bannerIsVisible = TRUE;
}

Swift 3

UIView.animate(withDuration: 5) {
self._addBannerDistanceFromBottomConstraint.constant = 0
self.view.layoutIfNeeded()
}

Constraint animation issue

Calling layoutIfNeeded on a view merely forces an update of the view early, rather than waiting for an update through the drawing cycle, when you animate your constraint you are placing the layoutIfNeeded inside the animation block thus animating the change, in the 2nd instance where you do not call layoutIfNeeded, the drawing cycle comes around and updates the view without animation.

Animation: Using Autolayout, Frame.Origin & Broken Constraints, Or 3rd Option?

In my opinion, the easiest solution is to leave your existing auto layout constraints. Finding an alternative auto layout arrangement can be a pain if you put a lot of work into making your layout adapt to various size classes.

So how do you keep your animation from breaking the constraints? Well as long as your view returns to its original position, animates off the screen, or is removed after the animation completes... take a picture!

I found this code in another thread:

extension UIView {
func takeSnapshot() -> UIImage {
UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.mainScreen().scale)

drawViewHierarchyInRect(self.bounds, afterScreenUpdates: true)

// old style: layer.renderInContext(UIGraphicsGetCurrentContext())

let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}

You can call it like this:

let picToAnimate = UIImageView(image: originalView.takeSnapshot())

Then make its size and location the same as what you took a picture of:

picToAnimate.frame = CGRectMake(originalView.frame.origin.x, originalView.frame.origin.y, originalView.frame.width, originalView.frame.height)
self.viewToAddItTo.addSubview(picToAnimate)

Don't forget to hide the original:

originalView.hidden = true

Hope this helps.

Auto Layout constraint change does not animate

I found the answer.

Instead of,

[settingsView layoutIfNeeded];

this line made it worked like charm,

[self.view layoutIfNeeded];

I suppose we need to perform layoutIfNeeded method on the parent view not just the view we are trying to animate.

UPDATE:
As pointed out in a comment by codyko, this is required for iOS 7, iOS 10.
For iOS 8 this issue does not exists.



Related Topics



Leave a reply



Submit