ios - How do I animate constraint changes?

ID : 1429

viewed : 174

Tags : iosobjective-canimationautolayoutios6ios





Top 5 Answer for ios - How do I animate constraint changes?

vote vote

96

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() } 
vote vote

82

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 UITextFields 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.

vote vote

80

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:

Xcode Project so sample animation project.

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

vote vote

60

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

57

Swift 4 solution

UIView.animate

Three simple steps:

  1. Change the constraints, e.g.:

    heightAnchor.constant = 50 
  2. Tell the containing view that its layout is dirty and that the autolayout should recalculate the layout:

    self.view.setNeedsLayout() 
  3. 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() } 

UIViewPropertyAnimator

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.

Top 3 video Explaining ios - How do I animate constraint changes?







Related QUESTION?