Setneedslayout VS. Setneedsupdateconstraints and Layoutifneeded VS Updateconstraintsifneeded

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.

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"

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

Why after -setNeedsLayout -layoutsSubviews method executes immediately

I've finally found an answer.
Thanks to warren-burton, to share a link of WWDC presentation, it really helps.

To answer to my question, we should firstly understand what really is the "Update cycle". It consist of three parts. Constraints part(to update all constraints), layout part(to set all frames) and drawing part(to draw a view on the screen). The main reason of misunderstanding in my question was that I don't know that

THE UPDATE CYCLE IS A LOOP

This loop is always running. That means potentially update cycle can run about 120 times per second. It was really bad performance if we would update constraints, layout and redraw 120 times every second. To avoid this problem there are a flags to update layout, update constraints and drawing. So, each update cycle the system checks in each part if it is necessary to call corresponding methods. And methods like updateConstraints(), layoutSubviews() and drawRect() calls only if the corresponding flag is on.

Can we affect these flags?

Yes, we can set the needed flag to true via setNeedsUpdateConstraints(), setNeedsLayout() and setNeedsDisplay() methods. So, if we call setNeedsLayout() method, the flag is set to true, and the layout will be updated in next update cycle.

Sample Image

Why after setNeedsLayout layoutsSubviews method executes immediately

It never executes immediately. It only seems to user that update cycle runs right after calling setNeedsLayout, because update cycle runs very fast and frequently.
The good example to understand is a changing constraint constant.

print(label.frame.height) // 100
labelHeightConstraint.constant = 200
print(label.frame.height) // 100

After we change the constraint, the system recalculates the frames and call the setNeedsLayout() to update frames in next update cycle. If you print the height of frame, it would be old, cause the update cycle doesn't occur yet. To see the calculated frame, you should force call the method immediately via layoutIfNeeded() method

print(label.frame.height) // 100
labelHeightConstraint.constant = 200
layoutIfNeeded()
print(label.frame.height) // 200

swift not sure on layoutIfNeeded setNeedsUpdateConstraints or updateConstraints setNeedsLayout

I would call view.setNeedsUpdateConstraints(). After viewDidLoad(), viewWillLayoutSubviews() should be called automatically. This should be all you need to do at this moment.

If you need to force the view to update immediately call view.layoutIfNeeded(), if you want to make a set of changes and then have them all applied at the next layout pass just call view.setNeedsLayout() at the end of all your changes.

Update constraints swift layoutIfNeeded doesn't work

Well, basic idea by @J. Doe is correct, here some code explanation (for simplicity i used UIView, not UICollectionView):

import UIKit

class ViewController: UIViewController {

@IBOutlet var heightConstraint: NSLayoutConstraint! // link the height constraint to your collectionView
private var height: CGFloat = 100 // you should keep constraint height for different view states somewhere

override func updateViewConstraints() {
heightConstraint.constant = height // set the correct height

super.updateViewConstraints()
}

// update height by button press with animation
@IBAction func increaseHight(_ sender: UIButton) {
height *= 2
UIView.animate(withDuration: 0.3) {
self.view.setNeedsUpdateConstraints()
self.view.layoutIfNeeded() // if you call `layoutIfNeeded` on your collectionView, animation doesn't work
}
}

}

Result:

Sample Image

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];
}];


Related Topics



Leave a reply



Submit