DBD DBD - 3 months ago 19
iOS Question

How do I animate constraint changes?

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 screen. Basic stuff.

Old style, I set the frame in an animation block.
New style, I have a
IBOutlet
to the constraint which determines the Y 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 WWDC12 video "Best Practices for Mastering Auto Layout" which covers animation. It discusses how to update constraints using
CoreAnimation
.

enter image description here
enter image description here

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.

Answer

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:

- (void)moveBannerOffScreen {
    [self.view layoutIfNeeded];

    _addBannerDistanceFromBottomConstraint.constant = -32;
    [UIView animateWithDuration:5
        animations:^{
            [self.view layoutIfNeeded]; // Called on parent view
        }];
    bannerIsVisible = FALSE;
}

- (void)moveBannerOnScreen { 
    [self.view layoutIfNeeded];

    _addBannerDistanceFromBottomConstraint.constant = 0;
    [UIView animateWithDuration:5
        animations:^{
            [self.view layoutIfNeeded]; // Called on parent view
        }];
    bannerIsVisible = TRUE;
}

And the Swift way....

_addBannerDistanceFromBottomConstraint.constant = 0

UIView.animateWithDuration(5) {
    self.view.layoutIfNeeded()
}