Expanding circle animated transition between view controllers

ios, uiviewanimation, uiviewanimationtransition

Solution

My first answer to this question was a modified version of an app I made a while ago, and would not be the best way to do it now (iOS 7 and beyond). In iOS 7 we can create custom transitions using the UIViewControllerTransitioningDelegate and associated delegates and classes. This version encapsulates all the mask creation and resizing in the animator object (the one that conforms to UIViewControllerAnimatedTransitioning), so it keeps the controller code simpler allowing you to use any controller as the destination controller with no special code. Here is the code I use in the class that's doing the presentation,

#import "ViewController.h"
#import "SecondViewController.h"
#import "RDPresentationAnimator.h"

@interface ViewController () <UIViewControllerTransitioningDelegate>
@property (weak,nonatomic) IBOutlet UIButton *button;
@end

@implementation ViewController

-(IBAction)presentSecondController:(UIButton *)sender {
    self.button = sender;
    SecondViewController *second = [self.storyboard instantiateViewControllerWithIdentifier:@"Second"];
    second.modalTransitionStyle = UIModalPresentationCustom;
    second.transitioningDelegate = self;
    [self presentViewController:second animated:YES completion:nil];
}

-(id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
    RDPresentationAnimator *animator = [RDPresentationAnimator new];
    animator.isPresenting = YES;
    animator.senderFrame = self.button.frame;
    return animator;
}


-(id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
    NSLog(@"dismissed delegate called");
    RDPresentationAnimator *animator = [RDPresentationAnimator new];
    animator.isPresenting = NO;
    animator.senderFrame = self.button.frame;
    return animator;
}

Here's the code in the animator object. The .h,

@interface RDPresentationAnimator : NSObject <UIViewControllerAnimatedTransitioning>

@property (nonatomic) BOOL isPresenting;
@property (nonatomic) CGRect senderFrame;

And here's the .m,

@interface RDPresentationAnimator ()
@property (strong,nonatomic) UIBezierPath *maskPath;
@property (strong,nonatomic) UIViewController *toVC;
@property (strong,nonatomic) UIViewController *fromVC;
//@property (strong,nonatomic) id <UIViewControllerContextTransitioning> transitionContext;
@end

    @implementation RDPresentationAnimator

    #define ANIMATION_TIME 0.6



    - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
        return ANIMATION_TIME;
    }


    - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
       // self.transitionContext = transitionContext;
        self.fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
        self.toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

        if (self.isPresenting) {
            [transitionContext.containerView addSubview:self.fromVC.view];
            [transitionContext.containerView addSubview:self.toVC.view];

            self.maskPath = [UIBezierPath bezierPathWithOvalInRect:self.senderFrame];
            CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
            maskLayer.frame = self.toVC.view.frame;
            maskLayer.path = self.maskPath.CGPath;
            self.toVC.view.layer.mask = maskLayer;

            UIBezierPath *newPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(-self.toVC.view.frame.size.width/2, -self.toVC.view.frame.size.height/2, self.toVC.view.frame.size.height*2, self.toVC.view.frame.size.height*2)];
            CABasicAnimation* pathAnim = [CABasicAnimation animationWithKeyPath: @"path"];
            //pathAnim.delegate = self;
            pathAnim.fromValue = (id)self.maskPath.CGPath;
            pathAnim.toValue = (id)newPath.CGPath;
            pathAnim.duration = ANIMATION_TIME;
            maskLayer.path = newPath.CGPath;
            [maskLayer addAnimation:pathAnim forKey:@"path"];
        }else{
            [transitionContext.containerView addSubview:self.toVC.view];
            [transitionContext.containerView addSubview:self.fromVC.view];
            self.maskPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(-self.fromVC.view.frame.size.width/2, -self.fromVC.view.frame.size.height/2, self.fromVC.view.frame.size.height*2, self.fromVC.view.frame.size.height*2)];
            CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
            maskLayer.frame = self.fromVC.view.frame;
            maskLayer.path = self.maskPath.CGPath;
            self.fromVC.view.layer.mask = maskLayer;

            UIBezierPath *newPath = [UIBezierPath bezierPathWithOvalInRect:self.senderFrame];
            CABasicAnimation* pathAnim = [CABasicAnimation animationWithKeyPath: @"path"];
            //pathAnim.delegate = self;
            pathAnim.fromValue = (id)self.maskPath.CGPath;
            pathAnim.toValue = (id)newPath.CGPath;
            pathAnim.duration = ANIMATION_TIME;
            maskLayer.path = newPath.CGPath;
            [maskLayer addAnimation:pathAnim forKey:@"path"];
        }

        [self performSelector:@selector(finishTransition:) withObject:transitionContext afterDelay:ANIMATION_TIME];
    }



    -(void)finishTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
        [transitionContext completeTransition:YES];
    }


    //- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
    //    NSLog(@"In didStop");
    //    if (flag) {
    //        if (self.isPresenting) {
    //            self.toVC.view.layer.mask = nil;
    //            [self.transitionContext completeTransition:YES];
    //        }else{
    //            self.fromVC.view.layer.mask = nil;
    //            [self.transitionContext completeTransition:YES];
    //            self.transitionContext = nil;
    //        }
    //    }
    //}


    -(void)dealloc {
        NSLog(@"In animator dealloc");
    }

This code also has the reverse animation for when you dismiss the modal controller (you only need to call dismissViewControllerAnimated:completion: like normal in that class).

Problem

I have created a custom animated transitioning using a transition delegate: ``` vc.modalTransitionStyle = UIModalPresentationCustom; vc.transitioningDelegate = self.transitioningDelegate; ``` The transitioning delegate is working perfectly, but I am trying to achieve an effect that I have not succeeded in doing thus far. I am trying to make the new view controller appear like this effect: Cool animated tranisitoning I tried doing CGAffineTransformMakeScale, but it zooms the new view controller view, rather than just "expanding the circle of view into it". When trying to change with animations the bound and frame, it always fits the circle in the end with the screen bounds, and doesn't completely open up the circle like in the animation I gave. Any ideas? Thanks.

Original source

Related problems