Search code examples
cocoa-touchuiviewuinavigationcontrollercore-animationuiviewanimationtransition

Why the pause between animateWithDuration animation and completion blocks?


Following Apple's recommendations, I'm chaining UIView animations by putting subsequent calls to -animationWithDuration:animation: in the completion: block of another call to aanimateWithDuration:animation:completion:, like so:

[UIView animateWithDuration:scaleDuration delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
    // Scale the controllers' views down.
    self.view.transform = CGAffineTransformScale(self.view.transform, 0.8, 0.8);
} completion:^(BOOL finished) {
    // Transition to the new view and push on the new view controller.
    [UIView transitionWithView:self.view duration:1 options:UIViewAnimationOptionCurveLinear | UIViewAnimationOptionTransitionFlipFromLeft animations:^{
        [self pushViewController:viewController animated:NO];
    } completion:^(BOOL finished) {
        [UIView animateWithDuration:scaleDuration delay:0 options:UIViewAnimationOptionCurveLinear animations:
^{
            // Scale back to the original size.
            self.view.transform = CGAffineTransformScale(self.view.transform, 1.25, 1.25);
        } completion:nil];
    }];
}];

The animations all execute the right order, but there is a tiny delay between them, especially before the -transitionWithView:duration:options:animations:completion: call. How do I smooth out the transitions between animation steps?


Solution

  • Aside:
    Is there any particular reason why you are abusing a navigation controller in this way? Couldn’t you just use presentViewController:animated:completion: setting its transition style to UIModalTransitionStyleFlipHorizontal?

    Back to your question:

    I’m pretty much sure that the stutter comes from the simple fact that pushViewController:animated: implicitly has to load the view of the view controller that is to be pushed and that this takes some time.

    So if you cannot use the presentViewController:animated:completion: (or presentModalViewController:animated: if you have to support iOS 4) approach, I’d encourage you to try this:

    // ensure the view is already loaded when you invoke `pushViewController:animated:`
    [viewController view];
    
    // there is no strict need to calculate those in situ, so we do it up front
    CGAffineTransform originalTransform = self.view.transform;
    CGAffineTransform downscalingTransform = CGAffineTransformScale(originalTransform, 0.8, 0.8);
    
    // I found it hard to read the block-in-a-block-in-a... while editing here, so I've taken them apart:
    void (^scaleDownAnimation)() = ^{
        self.view.transform = downscalingTransform;
    };
    
    void (^restoreScaleAnimation)() = ^{
        self.view.transform = originalTransform;
    };
    
    void (^pushControllerAnimation)() = ^{
        [self pushViewController:viewController animated:NO];
    };
    
    void (^pushAnimationCompletion)(BOOL) = ^(BOOL unused) {
        [UIView animateWithDuration:scaleDuration
                              delay:0
                            options:UIViewAnimationOptionCurveLinear
                         animations:restoreScaleAnimation
                         completion:nil];
    };
    
    void (^downscaleCompletion)(BOOL) = ^(BOOL unused){
        UIViewAnimationOptions linearFlipFromLeft = UIViewAnimationOptionCurveLinear | UIViewAnimationOptionTransitionFlipFromLeft;
        [UIView transitionWithView:self.view
                          duration:1
                           options:linearFlipFromLeft
                        animations:pushControllerAnimation
                        completion:pushAnimationCompletion];
    };
    
    [UIView animateWithDuration:scaleDuration
                          delay:0
                        options:UIViewAnimationOptionCurveEaseIn
                     animations:scaleDown
                     completion:downscaleCompletion];
    

    Note that the beef is within the first six lines, all the rest is just for completeness.