23

Readings:

From this answer:

This is what the accepted answer suggests to animate your view changes:

_addBannerDistanceFromBottomConstraint.constant = 0

UIView.animate(withDuration: 5) {
    self.view.layoutIfNeeded()
}

Why do we call layoutIfNeeded when we aren't changing the frames. We are changing the constraints, so (according to this other answer) shouldn't we instead be calling setNeedsUpdateConstraints?

Similarly this highly viewed answer says:

If something changes later on that invalidates one of your constraints, you should remove the constraint immediately and call setNeedsUpdateConstraints

Observations:

I actually did try using them both. Using setNeedsLayout my view animates correctly to the left

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func animate(_ sender: UIButton) {

        UIView.animate(withDuration: 1.8, animations: {
            self.centerXConstraint.isActive = !self.centerXConstraint.isActive
            self.view.setNeedsLayout()
            self.view.layoutIfNeeded()
        })
    }

    @IBOutlet weak var centerYConstraint: NSLayoutConstraint!
    @IBOutlet var centerXConstraint: NSLayoutConstraint!
}

However using setNeedsUpdateConstraints doesn't animate, It just moves the view rapidly to the left.

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func animate(_ sender: UIButton) {

        UIView.animate(withDuration: 1.8, animations: {
        self.centerXConstraint.isActive = !self.centerXConstraint.isActive
            self.view.setNeedsUpdateConstraints()
            self.view.updateConstraintsIfNeeded()    
        })
    }        

    @IBOutlet weak var centerYConstraint: NSLayoutConstraint!
    @IBOutlet var centerXConstraint: NSLayoutConstraint!
}

If I don't want animation then using either of view.setNeedsLayout or view.setNeedsUpdateConstraints move it to the left. However:

  • with view.setNeedsLayout, after my button is tapped, my viewDidLayoutSubviews breakpoint is reached. But the updateViewConstraints breakpoint is never reached. This leaves me baffled as to how the constraints are getting updated...
  • with view.setNeedsUpdateConstraints, after the button is tapped my updateViewConstraints breakpoint is reached and then the viewDidLayoutSubviews breakpoint is reached. This does make sense, the constraints are updated, then the layoutSubviews is called.

Questions:

Based on my readings: if you change constraints then for it to become effective you MUST call setNeedsUpdateConstraints, but based on my observations that's wrong. Having the following code was enough to animate:

self.view.setNeedsLayout()
self.view.layoutIfNeeded()

WHY?

Then I thought maybe somehow under the hoods it's updating the constraints through other means. So I placed a breakpoint at override func updateViewConstraints and override func viewDidLayoutSubviews but only the viewDidLayoutSubviews reached its breakpoint.

So how is the Auto Layout engine managing this?

abhimuralidharan
  • 5,752
  • 5
  • 46
  • 70
mfaani
  • 33,269
  • 19
  • 164
  • 293
  • 2
    None of the answers actually answer the question as to how animations work.. In iOS animations work on something called a presentation layer. It animates using a snapshot with interpolation from one place to another. Example: When you call `UIView.animate`, it takes a snapshot of the view to be animated. It takes note of all property values.. Then it calls your block which changes properties values.. It notes the difference in the values and interpolates over time, the snapshot from original values to new values (ones inside animation block).. When it is done, it will update your actual view. – Brandon Dec 26 '17 at 17:01
  • `updateViewConstraints` is called when constraints are implicitly updated, not explicitly changed. You didn't invalidate any constraints in an animation. Animate works on the layer's position and bounds. When you call `layoutIfNeeded`, it will layout the view on the next render cycle/pass (queue). Then you animate.. When you are done, it will layout your actual view and destroy the snapshot.. You will always notice your animation block only ever called once. `LayoutSubviews` will be called once at the END of your animation (because snapshot is animated, not view itself). – Brandon Dec 26 '17 at 17:15
  • @Brandon 1. "You didn't invalidate any constraints in an animation" Then What did I do here: `self.centerXConstraint.isActive = !self.centerXConstraint.isActive` Did I not invalidate a constraint?! 2. Or is it that this is an explicit change hence `updateViewConstraints` isn't called? ok it's not called, then what sort of callback does it trigger to complete the change/animation... – mfaani Dec 27 '17 at 00:15
  • You didn't invalidate anything. In your animation block, it only checks animatable properties. `.isActive` isn't animatable. Its state isn't snapshotted. Therefore, that property isn't actually animated, it's changed immediately. You then call `layoutIfNeeded`. The animation block will interpolate from its current position to the final position which was set when you modified `isActive`. All it does is animate the snapshot. Then it calls `layoutSubviews`. Try rotating your device and you'll see `updateConstraints` called. – Brandon Dec 27 '17 at 15:06
  • You didn't invalidate any constraints by saying `isActive = false`. That's not an invalid constraint. It's an in-active constraint and it still exists. An invalid constraint is a constraint that is left dangling such as when a view moves to a different super-view or device rotated, etc.. When a constraint is invalid, the system needs to recalculate the entire layout. It doesn't need to calculate your layout again, it just needs to move its position.. Also, `updateConstraints` is called on the view that is modified. It seems you are trying harder NOT to understand it than it is to understand. – Brandon Dec 27 '17 at 15:13
  • See this: https://pastebin.com/UnaP5eiQ . and the result: https://i.imgur.com/Wl9WRfV.png . You can see in the view hierarchy inspector, the actual `view` is already at its final position.. Yet in the simulator, you still see the layer `moving` towards the final position. It's just a snapshot layer that is moving.. The final values of your animation is already set long before your animation actually starts. Your constraints don't get animated. Your values aren't animated either. Notice `viewDidLayoutSubviews` is called once and called immediately even though animation is still happening.. – Brandon Dec 27 '17 at 15:48
  • You can see all of this if you press `Command-T` to slow down the animations.. but anyway, I've spent quite a bit of time explaining, I gotta go lol. Just check: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreAnimation_guide/CreatingBasicAnimations/CreatingBasicAnimations.html `All you have to do is update the properties of your layer object. When modifying layer objects in the layer tree, your changes are reflected immediately by those objects. However, the visual appearance of the layer objects does not change immediately.` `CA uses your changes as a trigger` – Brandon Dec 27 '17 at 15:51

3 Answers3

64

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:

Mischa
  • 15,816
  • 8
  • 59
  • 117
  • good answer. Thanks. Again this doesn't explicitly answer my two questions. Can you write a separate paragraph for its answer? – mfaani Dec 26 '17 at 22:16
  • Where do you address: "Then I thought maybe somehow under the hoods it's updating the constraints through other means. So I placed a breakpoint at override func updateViewConstraints and override func viewDidLayoutSubviews but only the viewDidLayoutSubviews reached its breakpoint. So how is the Auto Layout engine managing this?" – mfaani Dec 26 '17 at 22:43
  • I believe I did address this issue. For some clarification: updateViewConstraints() is only called during a layout pass when the constraints have been invalidated before. You can do this by calling setNeedsUpdateConstraints(). If you don’t call this method before, the system will assume that your constraints are still valid and skip the updateViewConstraints() call. Invalidating the layout is not the same as invalidating constraints. – Mischa Dec 26 '17 at 23:26
  • 1. "the system will assume that your constraints are still valid and skip the updateViewConstraints() call" This where I get confused. Have we updated a constraint or not?! Surely yes. 2. "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" So even in the edge cases, why would we need to call `setNeedsUpdateConstraints` doesn't it automatically happen?! – mfaani Dec 27 '17 at 00:27
  • 3. "If you don’t call this method before, the system will assume that your constraints are still valid and skip the updateViewConstraints() call" You mean even though I'm always guaranteed to get a deferred layout pass, ie this would always be called, the system won't update its constraints?! – mfaani Dec 27 '17 at 00:30
  • 1
    2. Exactly! Normally you don’t need to call setNeedsUpdateConstraints(). In the edge cases you’re talking about you do not change any constraint – you only change some state variable (like isCenteredLayout) internally and then you actually change your constraints based on that state inside you override of updateConstraints(). That’s why in this case you do meet to call setNeedsUpdateConstraints() beforehand because otherwise this method will never be called and no constraint will ever be changed. – Mischa Dec 27 '17 at 00:33
  • 1. Yes, you _have_ updated your constraints which means you don’t need to do that anymore. Thus, you don’t need to call updateConstraints() and thus, you don’t need to invalidate your constraints by calling setNeedsUpdateConstraints(). – Mischa Dec 27 '17 at 00:35
  • 1. hmmm. interesting. I mean I thought for *every* update you need to call `setNeedsUpdateConstraints`, but you're saying for every invalidating you need to call it. 2. THE PLACEMENT OF YOUR CODE WAS VITAL for my understanding. I'm learning this, normally what I do is I don't override any of these methods and try to make everything work using the button action. Can you elaborate/provide more examples on similar state variables + overrides – mfaani Dec 27 '17 at 00:47
  • 1
    1. Correct. You only call `setNeedsUpdateConstraints()` in case you do want to update your constraints as a batch inside `updateConstraints()` / `updateViewConstraints()`. Otherwise you never need to call this method. – Mischa Dec 27 '17 at 00:58
  • 2. That's the way to go. The state variable you choose depends on your particular layout. If you only have two possible layouts, a boolean state variable makes sense (e.g. `isCenteredLayout`). If you have three or more possible layouts you might want to define an `enum` and a case for each layout. Then you define a state variable of this type. There are many options if which you need to choose the most suitable for you. – Mischa Dec 27 '17 at 01:03
  • 3. Yes and no: A deferred layout pass does _not_ mean that `updateViewConstraints()` will be called. It will only be called during a layout pass if you have invalidated your constraints before by calling `setNeedsUpdateConstraints()`. – Mischa Dec 27 '17 at 01:06
  • so either A) when necessary batch ramifications of state variable changes using `setNeedsUpdateConstraints` OR B) make your changes and just **force** the layout using `layoutIfNeeded`. OK, say I do neither of these and simply put just switch `centerXConstraint.isActive` from `true` to `false`, this would be identical to the option A right as it has an implicit `setNeedsUpdateConstraints` right ? it won't happen immediately as a result it may or may not be what I expected... – mfaani Dec 27 '17 at 01:12
  • No, simply activating / deactivating a constraint will not trigger a `setNeedsUpdateConstraints()` call. Why should it? _You_ just updated the constraint! Why should the system need to update it again? – Mischa Dec 27 '17 at 11:01
  • Check this topic https://medium.com/@abhimuralidharan/ios-swift-setneedslayout-vs-layoutifneeded-vs-layoutsubviews-5a2b486da31c. It says, the `setNeedLayout` is an asynchronous event and `layoutIfNeeded` is synchronous. I understood in your answer, that the both are synchronous and immediate. Clearify please this terms in your answer. – Nike Kov Jul 08 '19 at 20:08
  • 1
    @NikKov: I know the article you referenced has a lot of "claps" and it states some important aspects of the two layout methods, but it leaves the reader under the impression that both methods do basically the same thing and you call either one or the other based on the concrete situation. This is wrong. As stated above, `setNeedsLayout()` simply invalidates the layout which _must_ be called if you want your view to be re-laid out. This re-layout happens automatically in the next deferred layout pass or when you call `layoutIfNeeded()`. – Mischa Jul 09 '19 at 08:59
  • 1
    @NikKov: In a way, `setNeedsLayout()` is asynchronous because it doesn't immediately layout the view. But if you call `layoutIfNeeded()` without having invalidated the layout before by calling `setNeedsLayout()`, nothing happens. Both methods work hand in hand, based on the _Principle of Invalidation_ explained in my answer. (_Note:_ When you add or remove constraints, the system calls `setNeedsLayout()` for you automatically, so usually you don't need to bother about that method.) Please refer to the linked resources in my answer for a deep dive. – Mischa Jul 09 '19 at 09:05
3

I will try to explain it simply:

The first thing to remember is that updating constraints does not cause the layout of views to be updated immediately. This is for performance reasons as laying everything out can take time so it 'makes note' of changes that need to take place then does a single layout pass.

Taking that one step further you can then not even update constraints when something affecting them changes but just flag that the constraints need to be updated. Even updating the constraints themselves (without laying out the views) can take time and the same ones could change both ways (i.e. active and inactive).

Now considering all that what setNeedsUpdateConstraints() does is to flag that the constraints for a view need to be re-calculated BEFORE the next layout pass because something about them has changed it doesn't make any constraint changes of affect the current layout at all. Then you should implement your own version of the updateConstraints() method to actually make the required changes to the constraints based on the current app state, etc.

So when the system decides the next layout pass should occur anything that has had setNeedsUpdateConstraints() called on it (or the system decides needs updating) will get its implementation of updateConstraints() called to make those changes. This will happen automatically before the laying out is done.

Now the setNeedsLayout() and layoutIfNeeded() are similar but for control of the actual layout processing itself.

When something that affects the layout of a view changes you can call setNeedsLayout() so that that view is 'flagged' to have it's layout re-calculated during the next layout pass. So if you change constraints directly (instead of perhaps using setNeedsUpdateConstraints() and updateConstraints()) you can then call setNeedsLayout() to indicate that the views layout has changed and will need to be re-calculated during the next layout pass.

What layoutIfNeeded() does is to force the layout pass to happen then and there rather than waiting for when the system determines it should next happen. It's that the forces the re-calculation of the layouts of views based on the current sate of everything. Note also that when you do this fist anything that has been flagged with setNeedsUpdateConstraints() will first call it's updateConstraints() implementation.

So no layout changes are made until the system decides to do a layout pass or your app calls layoutIfNeeded().

In practice you rarely need to use setNeedsUpdateConstraints() and implement your own version of updateConstraints() unless something is really complex and you can get by with updating view constraints directly and using setNeedsLayout() and layoutIfNeeded().

So in summary setNeedsUpdateConstraints doesn't need to be called to make constraint changes take affect and in fact if you change constraints they will automatically take affect when the system decides it's time for a layout pass.

When animating you want slightly more control over what is happening because you don't want an immediate change of the layout but to see it change over time. So for simplicity let's say you have an animation that takes a second (a view moves from the left of the screen to the right) you update the constraint to make the view move from left to right but if that was all you did it would just jump from one place to another when the system decided it was time for a layout pass. So instead you do something like the following (assuming testView is a sub view of self.view):

testView.leftPositionConstraint.isActive = false // always de-activate
testView.rightPositionConstraint.isActive = true // before activation
UIView.animate(withDuration: 1) {
    self.view.layoutIfNeeded()
}

Let's break that down:

First this testView.leftPositionConstraint.isActive = false turns off the constraint keeping the view in the left hand position but the layout of the view is not yet adjusted.

Second this testView.rightPositionConstraint.isActive = true turns on the constraint keeping the view in the right hand position but again the layout of the view is not yet adjusted.

Then you schedule the animation and say that during each 'time slice' of that animation call self.view.layoutIfNeeded(). So what that will do is force a layout pass for self.view every time the animation updates causing the testView layout to be re-calculated based on it's position through the animation i.e. after 50% of the animation the layout will be 50% between the stating (current) layout and the required new layout.

Thus doing that the animation takes affect.

So in overall summary:

setNeedsConstraint() - called to inform the system that the constraints of a view will need to be updated because something affecting them has changed. The constraints are not actually updated until the system decides a layout pass is needed or the user forces one.

updateConstraints() - this should be implemented for views to update the constraints based on the apps state.

setNeedsLayout() - this informs the system that something affecting the layout of a view (constraints probably) have changed and the layout will need to be re-calculated during the next layout pass. Nothing happens to the layout at that time.

layoutIfNeeded() - performs a layout pass for the view now rather than waiting for the next system scheduled one. At this point the view and it's sub views layouts will actually be re-calculated.

Edit to hopefully more directly answer the two questions:

1) Based on my readings: if you change constraints then for it to become effective you MUST call setNeedsUpdateConstraints, but based on my observations that's wrong. Having the following code was enough to animate:

self.view.setNeedsLayout()
self.view.layoutIfNeeded()

WHY?

First you have misunderstood in your readings you don't need to use setNeedsUpdateConstraints at all. Secondly they are enough (assuming they are in an animation block) because the setNeedsLayout() flags that self.view needs to have it's layout (and therefore its sub views layouts) re-calculated and the 'layoutIfNeeded()' forces the layout to take place immediately and therefore if inside an animation block to be done at each update of the animation.

2) Then I thought maybe somehow under the hoods it's updating the constraints through other means. So I placed a breakpoint at override func updateViewConstraints and override func viewDidLayoutSubviews but only the viewDidLayoutSubviews reached its breakpoint.

So how is the Auto Layout engine managing this?

Best to show with your original example of this:

_addBannerDistanceFromBottomConstraint.constant = 0

UIView.animate(withDuration: 5) {
    self.view.layoutIfNeeded()
}

The first line has updated the constraint by changing its constant (no need to use setNeedsUpdateConstraints) but the layout of the view (i.e. it's actual frame position and size) has not yet changed. When you call self.view.layoutIfNeeded() within the animation block that updates the layout of self.view which based on the current time frame of the animation. It is at this point that the frame position/size of views is calculated and adjusted.

I hope that makes it clearer but in reality your questions have been answered in detail in the body of the question maybe it was too detailed of an explanation though.

Now to help clarity EVERY view on the screen has a frame controlling both its size and position. This frame is either set manually via the property or is calculated using the constraints you have setup. Regardless of the method it's the frame that determines the position and size of the view not the constraints. The constraints are just used to calculate the frame of a view.

To try to make it even clearer I will now add two examples that achieve the same thing but using the two different methods. For both there is a testView which has constraints putting it in the centre of the main view controller view (these won't be changing and can effectively be ignored for the example). There is also a widthConstraint and a heightConstraint for that testView which will be used to control the height and width of the view. There is an expanded bool property which determines whether the testView is expanded or not and a testButton which is used to toggle between expanded and collapsed states.

The first way of doing it is this:

class ViewController: UIViewController {
    @IBOutlet var testView: UIView!
    @IBOutlet var testButton: UIButton!
    @IBOutlet var widthConstraint: NSLayoutConstraint!
    @IBOutlet var heightConstraint: NSLayoutConstraint!

    var expanded = false

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func testButtonAction(_ sender: Any) {
        self.expanded = !self.expanded

        if self.expanded {
            self.widthConstraint.constant = 200
            self.heightConstraint.constant = 200
        } else {
            self.widthConstraint.constant = 100
            self.heightConstraint.constant = 100
        }
        self.view.layoutIfNeeded() // You only need to do this if you want the layout of the to be updated immediately.  If you leave it out the system will decide the best time to update the layout of the test view.
    }

}

and here when the button is tapped the expanded bool property is toggled and then the constraints are immediately updated by changing their constants. layoutIfNeeded is then called to re-calculate the layout of the testView immediately (thus updating the display) although this could be left out leaving the system to re-calculate the layout based on the new constraint values when it needs to.

Now here is another way of doing the same thing:

class ViewController: UIViewController {
    @IBOutlet var testView: UIView!
    @IBOutlet var testButton: UIButton!
    @IBOutlet var widthConstraint: NSLayoutConstraint!
    @IBOutlet var heightConstraint: NSLayoutConstraint!

    var expanded = false

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func testButtonAction(_ sender: Any) {
        self.expanded = !self.expanded
        self.view.setNeedsUpdateConstraints()
    }

    override func updateViewConstraints() {
        super.updateViewConstraints()
        if self.expanded {
            self.widthConstraint.constant = 200
            self.heightConstraint.constant = 200
        } else {
            self.widthConstraint.constant = 100
            self.heightConstraint.constant = 100
        }
    }
}

and here when the button is tapped the 'expanded' bool property is toggled and we use updateConstraintsIfNeeded to flag to the system that the constraints will need to be updated before the layout can be re-calculated (whenever it may be the system determines that is needed). When the system needs to know those constraints to re-calculate the layout of the views (something it decides) it automatically calls updateViewConstraints and the constraints are changed at this time to their new values.

So if you try it these both do fundamentally the same thing as they stand but there are different use cases for them.

Using method 1 allows animation because (as has been noted) you can wrap the layoutIfNeeded in an animation block like this:

    UIView.animate(withDuration: 5) {
        self.view.layoutIfNeeded()
    }

which causes the system to animate between the initial layout and the new layout based on the constraint changes since the last time the layout was calculated.

Using method 2 allows you to postpone the need to change constraints until they are absolutely needed and you would want to do this when your constraints are really complex (lots of them) or there could be lots of actions that happen that could require the constraints to be changed before the next layout re-calculation is needed (to avoid continually changing constraints when not needed). Doing this though you lack the ability to animate the changes but that's probably not an issue as the complexity of the constraints would make everything slow to a crawl anyway.

I hope this helps more.

mfaani
  • 33,269
  • 19
  • 164
  • 293
Upholder Of Truth
  • 4,643
  • 2
  • 13
  • 23
  • 1. "The first thing to remember is that updating constraints does cause the layout of views to be updated immediately" Don't you mean "The first thing to remember is that updating constraints does **not** cause the layout of views to be updated immediately". 2. Can you give explicit answers to my questions? – mfaani Dec 26 '17 at 22:59
  • 1. Sorry yes I missed it out. Good spot as it completely alters the meaning. 2. I do answer the questions but maybe in too detailed a manner so I have updated the answer to address each more directly. – Upholder Of Truth Dec 26 '17 at 23:19
  • "The first line has updated the constraint by changing its constant (no need to use setNeedsUpdateConstraints)" OK. Can you give a simple example of where we need to change a constraint and then also MUST call `setNeedsUpdateConstraints`? – mfaani Dec 27 '17 at 00:33
  • Constraints can either be changed directly when required so `widthConstraint.constant = 100` in which case the constraint changes immediately and the view layout updated when required OR you can flag that constraints will need to be updated WHEN they are next required for layout calculations using `setNeedsUpdateConstraints` thus postponing the changes until the system actually needs them. Doing this then automatically calls `updateConstraints` when the system requires the constraints you don't need to call it yourself. – Upholder Of Truth Dec 27 '17 at 00:40
  • 1
    There is never a situation where you both change the constraint yourself and then call `setNeedsUpdateConstraints`. You do either one or the other. – Upholder Of Truth Dec 27 '17 at 00:41
2

setNeedsUpdateConstraints will update the constraints that will be changed based on a change you have made. For example if your view has a neighboring view with which there a constraint of horizontal distance, and that neighbor view got removed, the constraint is invalid now. In this case you should remove that constraint and call setNeedsUpdateConstraints. It basically makes sure that all your constraints are valid. This will not redraw the view. You can read more about it here.
setNeedsLayout on the other hand marks the view for redrawing and putting it inside animation block makes the drawing animated.

Santhosh R
  • 1,518
  • 2
  • 10
  • 14
  • "This will not redraw the view. You can read more about it here" <-- I want an answer. That's my question. It would be better if you elaborate a bit more or tell me which part of the link I should read. I've glimpsed through it before...Additionally you didn't reply to my 2nd question at the bottom – mfaani Dec 14 '17 at 23:50
  • @Honey Read "The Layout Process" section in the page. `setNeedsUpdateConstraints` or `updateViewConstraints` do not update the layout, but updates the "values" of constraints to be applied while laying out the view. But you already did that when you removed the center X constraint. `setNeedsLayout` is the one that marks the view for redrawing - meaning layout the view with its constraints. – Santhosh R Dec 20 '17 at 00:04
  • I read it again. Maybe I'm too daft...How does that address: "with view.setNeedsLayout, after my button is tapped, my viewDidLayoutSubviews breakpoint is reached. But the updateViewConstraints breakpoint is never reached. This leaves me baffled as to how the constraints are getting updated..." ? All I want to see is: **when I'm doing ** updating "the "values" of constraints to be applied while laying out the view" **where** would I get a callback for my constraints getting updated?! – mfaani Dec 20 '17 at 00:14
  • `updateViewConstraints` is **not** needed unless you want to revalidate the changes in constraints. You have **already** changed the constraints so you **don't need to call `updateViewConstraints`** again. If you did not remove your constraint but lets say removed a neighboring view which will affect the constraints of your actual view, thats when you **need** to call `updateViewConstraints`. So `layout` only redraws the view it doesn't bother to call `updateViewConstraints` as it believes the existing constraints are valid. – Santhosh R Dec 20 '17 at 00:22
  • "If you did not remove your constraint..." Actually, once you remove a subview, its constraints also get removed *automatically*. See [here](https://stackoverflow.com/questions/18617991/what-happens-with-constraints-when-a-view-is-removed). My apologies, I think you don't properly understand my question or I don't understand your answer. I'll revisit your answer and the link again with a fresh mind. Hopefully I'll understand then. Thank you for your help. – mfaani Dec 20 '17 at 00:37
  • Can you explicitly answer the 2 questions I've wrote at the end? – mfaani Dec 24 '17 at 09:39
  • setNeedsUpdateConstraints is used to inform the system that a views constraints need to be updated not that they have been. This can then take place in an implementation of updateConstraints. setNeedsLayout() is used to inform the system that something affecting the layout of a views sub views has changed and the layout needs to be re-calculated. – Upholder Of Truth Dec 26 '17 at 17:03