2023 answer
The correct solution was given in a comment, you need to call layoutIfNeeded()
A typical example
UIView.animate(withDuration: t) { [weak self] in
guard let self = self else { return }
self.bottom.constant = -H
self.superview!.layoutIfNeeded()
}
IMPORTANT: "IN OR OUT" IS THE SAME
- somewhat bizarrely, it makes NO difference if you do:
this
UIView.animate(withDuration: t) {
self.bottom.constant = -66
self.superview!.layoutIfNeeded()
}
or this
self.bottom.constant = -66
UIView.animate(withDuration: t) {
self.superview!.layoutIfNeeded()
}
There are 1000 totally incorrect comments on SO that you have to do it one way or the other to "mak eit work". It has nothing to do with the issue.
WHY THE SOLUTION IS layoutIfNeeded
It was perfectly explained in the @GetSwifty comment.
layoutIfNeeded is when the actual animation takes place. setNeedsLayout just tells the view it needs to layout, but doesn't actually do it.
WHY THE ISSUE IS CONFUSING: IT "SOMETIMES" WORKS ANYWAY
Often you actually do not need to explicitly have layoutIfNeeded
. This causes lots of confusion. ie, this will work "sometimes" ...
UIView.animate(withDuration: t) {
self.bottom.constant = -66
}
There are particular things to consider:
If you are animating "the view itself" (ie likely in a custom UIView subclass), the cycle can be different from when you are animating some view below you "from above"
If you have other animations going on, that can affect the view cycle.
Note that if it "magically works" without layoutIfNeeded
, it may NOT work other times in your app, depending on what's going on in the app.
In short you must always add layoutIfNeeded
, that's all there is to it.