How do I animate constraint changes?

后端 未结 12 1673
野的像风
野的像风 2020-11-21 23:22

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.

O

相关标签:
12条回答
  • 2020-11-21 23:56

    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()
    }
    
    0 讨论(0)
  • 2020-11-21 23:57

    I was trying to animate Constraints and was not really easy to found a good explanation.

    What other answers are saying is totally true: you need to call [self.view layoutIfNeeded]; inside animateWithDuration: animations:. However, the other important point is to have pointers for every NSLayoutConstraint you want to animate.

    I created an example in GitHub.

    0 讨论(0)
  • 2020-11-21 23:58
    // 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];
    }];
    
    0 讨论(0)
  • 2020-11-21 23:59

    In the context of constraint animation, I would like to mention a specific situation where I animated a constraint immediately within a keyboard_opened notification.

    Constraint defined a top space from a textfield to top of the container. Upon keyboard opening, I just divide the constant by 2.

    I was unable to achieve a conistent smooth constraint animation directly within the keyboard notification. About half the times view would just jump to its new position - without animating.

    It occured to me there might be some additional layouting happening as result of keyboard opening. Adding a simple dispatch_after block with a 10ms delay made the animation run every time - no jumping.

    0 讨论(0)
  • 2020-11-22 00:00

    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.

    0 讨论(0)
  • 2020-11-22 00:03

    There is an article talk about this: http://weblog.invasivecode.com/post/42362079291/auto-layout-and-core-animation-auto-layout-was

    In which, he coded like this:

    - (void)handleTapFrom:(UIGestureRecognizer *)gesture {
        if (_isVisible) {
            _isVisible = NO;
            self.topConstraint.constant = -44.;    // 1
            [self.navbar setNeedsUpdateConstraints];  // 2
            [UIView animateWithDuration:.3 animations:^{
                [self.navbar layoutIfNeeded]; // 3
            }];
        } else {
            _isVisible = YES;
            self.topConstraint.constant = 0.;
            [self.navbar setNeedsUpdateConstraints];
            [UIView animateWithDuration:.3 animations:^{
                [self.navbar layoutIfNeeded];
            }];
        }
    }
    

    Hope it helps.

    0 讨论(0)
提交回复
热议问题