How do I animate constraint changes?
Solution 1
Two important notes:
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 completedYou 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()
}
Solution 2
I appreciate the answer provided, but I think it would be nice to take it a bit further.
The basic block animation from the documentation
[containerView layoutIfNeeded]; // Ensures that all pending layout operations have been completed
[UIView animateWithDuration:1.0 animations:^{
// Make all constraint changes here
[containerView layoutIfNeeded]; // Forces the layout of the subtree animation block and then captures all of the frame changes
}];
but really this is a very simplistic scenario. What if I want to animate subview constraints via the updateConstraints
method?
An animation block that calls the subviews updateConstraints method
[self.view layoutIfNeeded];
[self.subView setNeedsUpdateConstraints];
[self.subView updateConstraintsIfNeeded];
[UIView animateWithDuration:1.0f delay:0.0f options:UIViewAnimationOptionLayoutSubviews animations:^{
[self.view layoutIfNeeded];
} completion:nil];
The updateConstraints method is overridden in the UIView subclass and must call super at the end of the method.
- (void)updateConstraints
{
// Update some constraints
[super updateConstraints];
}
The AutoLayout Guide leaves much to be desired but it is worth reading. I myself am using this as part of a UISwitch
that toggles a subview with a pair of UITextField
s with a simple and subtle collapse animation (0.2 seconds long). The constraints for the subview are being handled in the UIView subclasses updateConstraints methods as described above.
Solution 3
Generally, you just need to update constraints and call layoutIfNeeded
inside the animation block. This can be either changing the .constant
property of an NSLayoutConstraint
, adding remove constraints (iOS 7), or changing the .active
property of constraints (iOS 8 & 9).
Sample Code:
[UIView animateWithDuration:0.3 animations:^{
// Move to right
self.leadingConstraint.active = false;
self.trailingConstraint.active = true;
// Move to bottom
self.topConstraint.active = false;
self.bottomConstraint.active = true;
// Make the animation happen
[self.view setNeedsLayout];
[self.view layoutIfNeeded];
}];
Sample Setup:
Controversy
There are some questions about whether the constraint should be changed before the animation block, or inside it (see previous answers).
The following is a Twitter conversation between Martin Pilkington who teaches iOS, and Ken Ferry who wrote Auto Layout. Ken explains that though changing constants outside of the animation block may currently work, it's not safe and they should really be change inside the animation block. https://twitter.com/kongtomorrow/status/440627401018466305
Animation:
Sample Project
Here's a simple project showing how a view can be animated. It's using Objective C and animates the view by changing the .active
property of several constraints.
https://github.com/shepting/SampleAutoLayoutAnimation
Solution 4
// Step 1, update your constraint
self.myOutletToConstraint.constant = 50; // New height (for example)
// Step 2, trigger animation
[UIView animateWithDuration:2.0 animations:^{
// Step 3, call layoutIfNeeded on your animated view's parent
[self.view layoutIfNeeded];
}];
Solution 5
Swift 4 solution
Three simple steps:
-
Change the constraints, e.g.:
heightAnchor.constant = 50
-
Tell the containing
view
that its layout is dirty and that the autolayout should recalculate the layout:self.view.setNeedsLayout()
-
In animation block tell the layout to recalculate the layout, which is equivalent of setting the frames directly (in this case the autolayout will set the frames):
UIView.animate(withDuration: 0.5) { self.view.layoutIfNeeded() }
Complete simplest example:
heightAnchor.constant = 50
self.view.setNeedsLayout()
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
}
Sidenote
There is an optional 0th step - before changing the constraints you might want to call self.view.layoutIfNeeded()
to make sure that the starting point for the animation is from the state with old constraints applied (in case there were some other constraints changes that should not be included in animation):
otherConstraint.constant = 30
// this will make sure that otherConstraint won't be animated but will take effect immediately
self.view.layoutIfNeeded()
heightAnchor.constant = 50
self.view.setNeedsLayout()
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
}
Since with iOS 10 we got a new animating mechanism - UIViewPropertyAnimator
, we should know that basically the same mechanism applies to it. The steps are basically the same:
heightAnchor.constant = 50
self.view.setNeedsLayout()
let animator = UIViewPropertyAnimator(duration: 0.5, timingParameters: UICubicTimingParameters(animationCurve: .linear))
animator.addAnimations {
self.view.layoutIfNeeded()
}
animator.startAnimation()
Since animator
is an encapsulation of the animation, we can keep reference to it and call it later. However, since in the animation block we just tell the autolayout to recalculate the frames, we have to change the constraints before calling startAnimation
. Therefore something like this is possible:
// prepare the animator first and keep a reference to it
let animator = UIViewPropertyAnimator(duration: 0.5, timingParameters: UICubicTimingParameters(animationCurve: .linear))
animator.addAnimations {
self.view.layoutIfNeeded()
}
// at some other point in time we change the constraints and call the animator
heightAnchor.constant = 50
self.view.setNeedsLayout()
animator.startAnimation()
The order of changing constraints and starting an animator is important - if we just change the constraints and leave our animator for some later point, the next redraw cycle can invoke autolayout recalculation and the change will not be animated.
Also, remember that a single animator is non-reusable - once you run it, you cannot "rerun" it. So I guess there is not really a good reason to keep the animator around, unless we use it for controlling an interactive animation.
Comments
-
DBD about 3 years
I'm updating an old app with an
AdBannerView
and when there is no ad, it slides off screen. When there is an ad it slides on the screen. Basic stuff.Old style, I set the frame in an animation block. New style, I have a
IBOutlet
to the auto-layout constraint which determines theY
position, in this case it's distance from the bottom of the superview, and modify the constant:- (void)moveBannerOffScreen { [UIView animateWithDuration:5 animations:^{ _addBannerDistanceFromBottomConstraint.constant = -32; }]; bannerIsVisible = FALSE; } - (void)moveBannerOnScreen { [UIView animateWithDuration:5 animations:^{ _addBannerDistanceFromBottomConstraint.constant = 0; }]; bannerIsVisible = TRUE; }
And the banner moves, exactly as expected, but no animation.
UPDATE: I re-watched WWDC 12 talk Best Practices for Mastering Auto Layout which covers animation. It discusses how to update constraints using CoreAnimation:
I've tried with the following code, but get the exact same results:
- (void)moveBannerOffScreen { _addBannerDistanceFromBottomConstraint.constant = -32; [UIView animateWithDuration:2 animations:^{ [self.view setNeedsLayout]; }]; bannerIsVisible = FALSE; } - (void)moveBannerOnScreen { _addBannerDistanceFromBottomConstraint.constant = 0; [UIView animateWithDuration:2 animations:^{ [self.view setNeedsLayout]; }]; bannerIsVisible = TRUE; }
On a side note, I have checked numerous times and this is being executed on the main thread.
-
abbood over 10 yearsI've never seen so many votes offered for a question and answer on a typo on SO before
-
jamil ahmed over 9 yearsIf there is a typo in the answer, you should edit the answer. That's why they're editable.
-
DBD over 9 years@jeffamaphone - It would be more useful if you pointed out the typo so I knew where the mistake was. You could edit the answer yourself and fixed the typo saving everyone else our diatribe. I did just edit it to remove the constant from the animation block, if that's what you were referring to.
-
jamil ahmed over 9 yearsI don't know what the typo is. I was responding to comments above.
-
DBD over 9 yearsThen the typo is the question. Stupidly I was typing "setNeedsLayout" instead of "layoutIfNeeded". It's shown clearly in my question when I cut and paste my code with the error and the screenshots with the correct command. Yet couldn't seem to notice it til someone pointed it out.
-
-
g3rv4 over 11 yearsYou have tried doing the change in the constant and layoutIfNeeded both inside the animation block, right?
-
DBD over 11 yearsyes, I've tried that. I had it that way originally and only modified after a second watch on the WWDC video.
-
DBD over 11 yearsThere are no other constraint changes. The constraints on the ad banner are distance from left edge(0), distance from right edge(0), fixed height(32) and distance from bottom(0). There are no other constraints associated with the banner. Commenting out the constraint change will means nothing occurs.
-
DBD over 11 yearsYou know what... your answer works. The WWDC works.... my vision fails. For some reason it took me a week to realize I was calling
setNeedsLayout
instead oflayoutIfNeeded
. I'm slightly horrified by how many hours I spent not noticing I just just typed the wrong method name. -
Ortwin Gentz over 11 yearsThe solution works but you don't need to change the constraint constant within the animation block. It's totally fine to set the constraint once before kicking off the animation. You should edit your answer.
-
Oliver Pearmain about 11 yearsThis didn't work for me initially and then I realized you need to call layoutIfNeeded on the PARENT view, not the view the constraints apply to.
-
ngb over 10 yearsusing layoutIfNeeded will animate all the subview refreshes not just the constraint change. how do u animate the constraint change only?
-
anneblue about 10 yearsWhen calling all the methods above on
self.view
(and not on a subview of that view), callingupdateConstraintsIfNeeded
is not necessary (becausesetNeedsLayout
also triggersupdateConstraints
of that view). Might be trivial for the most, but it was not for me until now ;) -
Cameron Lowell Palmer about 10 yearsIt is hard to comment without knowing the full interplay of Autolayout and springs and struts layout in your particular case. I'm speaking entirely about a pure autolayout scenario. I would be curious to know what exactly is happening in your layoutSubviews method.
-
Cameron Lowell Palmer about 10 yearstranslatesAutoresizingMaskIntoConstraints = NO; If you want to pure-autolayout you should definitely disable this.
-
Cameron Lowell Palmer about 10 yearsI haven't seen that, but I can't really speak to the issue without some code. Maybe you could mock up a TableView and stick it on GitHub?
-
Robert over 9 yearsBeware: If using the
UIViewAnimationOptionBeginFromCurrentState
the layout constraints will be set BEFORE the animation! -
natbro over 9 yearsworks great - thanks. but just so we're clear: it is pure insanity for constraints to basically exist in an entirely different scope for animation. this really guts some very clean older animation logic.
-
Jesse about 9 yearsI couldnt get this to working, thinking i had to call layoutIfNeeded on the parent view of the constraint. I changed it to the parent view of the view controller and now its works beautifully
-
Victor almost 9 yearsAlso be careful with
UILabel
s. Some type animation with UILabel works differently against the same code with simple UIView . Unfortunately, I don't know why. And if someone knows about it I really appreciate to comment me. -
user3344977 almost 9 yearsThis works perfectly and I'd like to note I'm calling layoutIfNeeded on the subview, not it's parent view. Not really sure why are others are having to call it on the parent.
-
Rick van der Linde over 8 years"Apple actually recommends you call it once before the animation block to ensure that all pending layout operations have been completed", thank you, never thought about that, but it makes sense.
-
Korey Hinton over 8 years+1 for showing an example using the new active flag. Also, changing the constraint outside the animation block always felt like a hack to me
-
malhal over 8 yearsI raised an issue in github because this solution doesn't work for moving the view back to where it was. I believe changing active isn't the correct solution and you should be changing the priority instead. Set the top to 750, and the bottom to 250, then in code alternate between UILayoutPriorityDefaultHigh and UILayoutPriorityDefaultLow.
-
James Toomey almost 8 yearsI liked this but wanted the EaseIn effect, and found this works nicely in Swift: UIView.animateWithDuration(0.3, delay: 0, options: UIViewAnimationOptions.CurveEaseIn, animations: { self.view.layoutIfNeeded() }, completion: nil)
-
Chris Conover almost 8 yearsAnother reason for updating inside the block is that it isolates the changes from calling code that may happen to also call layoutIfNeeded. (and thanks for the Twitter link)
-
Jonny over 7 yearsThis does not work with related constraints. Let's say we animate a height A with this, height B that is dependent on height A will be shorter when height A is long, and longer when height A is short. B will unfortunately change right away and will NOT animate.
-
He Yifei 何一非 over 7 yearsSometimes the animation don't animate and run straight to completion. How to solve it?
-
Rool Paap over 7 yearsI've implemented your solution multiple times successfully, today it wouldn't work for a new implementation. Found out you need to call
self.view.layoutIfNeeded()
in the animation block, NOT the completion block. Doh! -
Rob over 7 yearsThe "Apple actually recommends" link is broken. If someone knows the new URL can they please update?
-
art-divin over 7 yearsCode example would look better if in the callback one would call
[subview.superview layoutIfNeeded];
-
Viktor Kucera almost 7 years@ngb Setup all constraint you don't want to animate -> call
layoutIfNeeded
and then setup animation block with that one constraint you want to animate, anotherlayoutIfNeeded
included. -
Chauyan over 6 yearsYes, it works on iOS 11 beta5, and dont need to update the constraint inside animation block.
-
Eugenio over 6 yearsInitially it didn't worked for me but then I realised that I've to put isHidden = false out of the animation block and inside the completion block instead
-
Jona over 6 yearsGreat answer. Specially because you mention Martin's and Ken's conversation about this.
-
mfaani over 6 yearsI'm confused about the manner of updating constraints and wrote this. Can you please take a look?
-
mfaani over 6 yearsI'm confused about the manner of updating constraints and wrote this. Can you please take a look?
-
mfaani over 6 yearsI'm confused about the manner of updating constraints and wrote this. Can you please take a look?
-
Medhi about 4 yearslayoutIfNeeded() is the key
-
Paul T. over 3 yearsthat's not correct. Constraints should be updated outside of the animation block and inside the animation block you should have only view.layoutIfNeeded()
-
Paul T. over 3 yearsthat's not correct. Constraints should be updated outside of the animation block and inside the animation block you should have only view.layoutIfNeeded()