UIViewController transition animation

August 30, 2015

UIViewController transition animation

Sometimes we need to spice default UIViewController transition animation a little bit up. Thus we need to write our custom one. Here I will guide you through the process of creating custom UIViewController transition animation.

... in case you don't want to create your own animator object, you can use my customizable class, which will get you exactly that transition animation: ARSSlideTransition.

Creating animator object

Implementing header file and class extension

We are creating Animator class, which is a subclass of NSObject and conforms to a UIViewControllerAnimatedTransitioning protocol. We need as well one property of type UINavigationControllerOperation - that's where we will store the type of transition triggered from our UIViewController.

@interface Animator : NSObject <UIViewControllerAnimatedTransitioning>

@property (nonatomic) UINavigationControllerOperation operation;

@end

Also it would be nice to store transitionContext object as a property for furhter use in different method.

@interface Animator ()

@property (weak, nonatomic) id<UIViewControllerContextTransitioning> context;

@end
Animation duration

Next, we need to implement couple of required methods, since we are conforming to this transition protocol. First method is transitionDuration:, where we are going to return our animation duration.

- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
    return 0.6;
}
Getting and configuring UIViews before animation

Now we are implementing second method, where we are going to prepare our UIViews for animation. Here is what we need to do:

  • First of all we need to save transitionContext object, then we are going to get UIViewControllers that participate in animation
  • Next we calculate animation direction depending on type of operation we have.
  • The very last step in this method - we are moving the view, to which we are going to be transitioning to, offscreen, in order to perform this slide animation of it's containing UIViews.

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
    self.context = transitionContext;

    id fromVC = [self.context viewControllerForKey:UITransitionContextFromViewControllerKey];
    id toVC = [self.context viewControllerForKey:UITransitionContextToViewControllerKey];
    [[self.context containerView] addSubview:[toVC view]];

    CGFloat delta = CGRectGetWidth([toVC view].frame);
    delta = UINavigationControllerOperationPush == self.operation ? delta : delta * (-1);

    [toVC view].frame = [self.context finalFrameForViewController:toVC];
    [toVC view].transform = CGAffineTransformMakeTranslation(delta, 0);

    [self animationFromViewController:fromVC toViewController:toVC delta:delta];
}
Animation itself

Now we are implementing our helper method, where we are going to encapsulate our animation logic.

- (void)animationFromViewController:(id)fromVC toViewController:(id)toVC delta:(CGFloat)delta {
    __block NSTimeInterval timeOffset = self.initialDelay ? self.initialDelay : 0.0;

    [UIView animateWithDuration:0.0 delay:0.0 options:0.6 animations:nil completion:^(BOOL finished) {
        for (UIView *object in [fromVC objectsToAnimate]) {
            [UIView animateWithDuration:0.6 delay:timeOffset usingSpringWithDamping:0.8 initialSpringVelocity:0.8 options:UIViewAnimationOptionCurveEaseOut animations:^{
                object.transform = CGAffineTransformMakeTranslation(-delta, 0);
            } completion:nil];

            timeOffset += 0.05;
        }

        timeOffset = 0.15;
        for (NSInteger i = 0; i < [toVC objectsToAnimate].count; i++) {
            UIView *object = [[toVC objectsToAnimate] objectAtIndex:i];
            BOOL isLastObject = i == [toVC objectsToAnimate].count - 1;
            void(^completionBlock)(BOOL) = !isLastObject ? nil : ^(BOOL finished) {
                [self.context completeTransition:YES];
            };

            [UIView animateWithDuration:0.6 delay:timeOffset usingSpringWithDamping:0.8 initialSpringVelocity:0.8 options:UIViewAnimationOptionCurveEaseOut animations:^{
                object.transform = CGAffineTransformMakeTranslation(-delta, 0);
            } completion:completionBlock];

            timeOffset += 0.05;
        }
    }];
}

I suppose you are wondering why we need to encapsulate our UIView animation calls inside another UIView animation block? I have figured, that in order to load UITableViewCells from the destination controller, I had to embedded my animation calls inside that animation completion block. When I didn't do so, my destination controller's cells were not initialized correctly. In case you have ideas why this works this specific way - please let me know in comments bellow.

Completing animation

When animation completes, we need to position all our UIViews to their original positions. We do it with this delegate method, that gets called when UIView animation is completed.

- (void)animationEnded:(BOOL)transitionCompleted {
    id viewController = [self.context viewControllerForKey:UITransitionContextToViewControllerKey];

    [viewController view].transform = CGAffineTransformIdentity;
    for (UIView *object in [viewController objectsToAnimate]) {
        object.transform = CGAffineTransformIdentity;
    }
}

Configuring presented UIViewController

In UIViewController to which you are going to transition to, we have to implement objectsToAnimate method, that we are going to call to get array of views we want to animate.

- (NSArray *)objectsToAnimate {
    return [self.tableView visibleCells];
}

Configuring presenting UIViewController

In order to tell the system that we are going to use a custom transition, we have to do a couple things:

  • conform to UINavigationControllerDelegate
  • specify the class you wish to be a delegate
  • implement navigationController:animationControllerForOperation: fromViewController:toViewController: method
  • implement objectsToAnimate method, that we are going to call to get array of views we want to animate

@interface ViewController () <UINavigationControllerDelegate>

- (void)viewDidLoad {
    [super viewDidLoad];

    self.navigationController.delegate = self;
}

- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
    Animator *transition = [Animator new];
    transition.operation = operation;
    return transition;
}

- (NSArray *)objectsToAnimate {
    return [self.tableView visibleCells];
}

Conclusion

There you have it, a nice UIViewController transition animation, that is definitely gives some points to your app's coolness. However, you cannot interact with this kind of animation. I will review animations that you can interact with in another article.

Comments

comments powered by Disqus