After iOS 7, Apple opened the relevant API for custom transitions. Now it is nearly iOS 12, but we have not studied transitions properly. One is that we did not pay attention to them before and thought it was flashy, and the other is that there is no requirement for such transitions in our projects. The transition animation mentioned here is not the same as the CATransition animation in the previous CAAnimation system animation. The last one refers to the transition effect of a single View, and here refers to the transition effect of the whole controller. In fact, the present article is also to lay a foundation for today, the complex transition effect is also composed of a single animation.

As you can see from figure to complete custom transitions animation, must obey UIViewControllerAnimatedTransitioning agreement, the agreement has two must implement method is a return to the ferry time, one is the realization of specific transitions. This article will combine five of the most commonly used animation scenes to illustrate transitions.

First, take a look at the transition effect of Yanxuan App of netease. It can be seen that when the current page wants to Push other pages, the current page will sink and other pages will move from the right to the left. When the Present page is presented, the current page also sinks and the target view pops up from the bottom.

In the ViewController, there are two buttons: Push SecondVC and Present ThirdVC.

- (IBAction)pushBtnClick:(id)sender
{
    SecondViewController * vc = [[SecondViewController alloc] init];
    [self.navigationController pushViewController:vc animated:YES];
}


- (IBAction)presentBtnClick:(id)sender
{
    ThirdViewController * vc = [[ThirdViewController alloc] init];
    [self presentViewController:vc animated:YES completion:nil];
}

Copy the code

Push and Pop animations

UIViewControllerAnimatedTransitioning agreement

Here a new AnimatedTransitioningObject class, and then to follow UIViewControllerAnimatedTransitioning agreement. The convenience, the Push, Pop, the Present, Dismiss the four effect to write together, to distinguish with the enumeration,, of course, also can put each animation effects using a AnimatedTransitioningObject class itself.

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

typedef NS_ENUM(NSInteger,TransitionAnimationObjectType) {
    TransitionAnimationObjectType_Push,
    TransitionAnimationObjectType_Pop,
    TransitionAnimationObjectType_present,
    TransitionAnimationObjectType_Dismiss
};

@interface TransitionAnimationObject : NSObject <UIViewControllerAnimatedTransitioning>

@property (nonatomic,assign) TransitionAnimationObjectType type;

- (instancetype)initWithTransitionAnimationObjectType:(TransitionAnimationObjectType)type;

+ (instancetype)initWithTransitionAnimationObjectType:(TransitionAnimationObjectType)type;

@end
Copy the code

Let’s take a look at the two methods that must be implemented. In return for the transition time, you can also return different animation time according to type, which is always 0.5 seconds. PushAnimateTransition implements Push effect transitions.

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

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
    switch (_type) {
        case TransitionAnimationObjectType_Push:
            [self pushAnimateTransition:transitionContext];
            break;

        case TransitionAnimationObjectType_Pop:
            [self popAnimateTransition:transitionContext];
            break;

        case TransitionAnimationObjectType_present:
            [self presentAnimateTransition:transitionContext];
            break;

        case TransitionAnimationObjectType_Dismiss:
            [self dismissAnimateTransition:transitionContext];
            break;

        default:
            break; }}Copy the code

Push to realize

- (void) pushAnimateTransition: (id < UIViewControllerContextTransitioning >) transitionContext {/ / View (secondVC. View) and get goals Source View (ViewController. View) UIView * toView = [transitionContext viewForKey: UITransitionContextToViewKey]; UIView * fromView = [transitionContext viewForKey:UITransitionContextFromViewKey]; / / screenshots here do animation hidden source View UIView * tempView = [fromView snapshotViewAfterScreenUpdates: NO]; fromView.hidden = YES; UIView * containerView = [transitionContext containerView]; [containerView addSubview:tempView]; [containerView addSubview:toView]; CGFloat width = containerView.frame.size.width; CGFloat height = containerView.frame.size.height; Toview. frame = CGRectMake(width, 0, width, height); toview. frame = CGRectMake(width, 0, width, height); / / start to do animation NSTimeInterval duration = [self transitionDuration: transitionContext]; [UIView animateWithDuration: duration animations: ^ {tempView. Transform = CGAffineTransformMakeScale (0.9, 0.9);  toView.transform = CGAffineTransformMakeTranslation(-width, 0); } completion:^(BOOL finished) {// Complete context completeTransition:! [transitionContext transitionWasCancelled]]; // Transitions should be cancelled accordinglyif([transitionContext transitionWasCancelled]) { fromView.hidden = NO; [tempView removeFromSuperview]; }}]; }Copy the code

Pop implementation

Push and Pop is relative relationship, so in the Pop animation, objective view and source view swap identity, CGAffineTransformIdentity implementation is also used to restore a Push animation.

- (void) popAnimateTransition: (id < UIViewControllerContextTransitioning >) transitionContext {/ / note that there is reduction So toView and fromView Identity swaps toView is ViewController. View UIView * toView = [transitionContext viewForKey: UITransitionContextToViewKey]; UIView * fromView = [transitionContext viewForKey:UITransitionContextFromViewKey]; UIView * containerView = [transitionContext containerView]; UIView * tempView = [[containerView subviews] firstObject]; [containerView insertSubview:toView belowSubview:fromView] [containerView insertSubview: fromView] / / the animation can be direct reduction NSTimeInterval duration = [self transitionDuration: transitionContext]; [UIView animateWithDuration:duration animations:^{ tempView.transform = CGAffineTransformIdentity;  fromView.transform = CGAffineTransformIdentity; } completion:^(BOOL finished) {// Complete Context completeTransition:! TransitionContext. TransitionWasCancelled]; / / to successful treatmentif (![transitionContext transitionWasCancelled])
        {
            [tempView removeFromSuperview];
            toView.hidden = NO;
        }
    }];
}

Copy the code

UINavigationControllerDelegate proxy method

After finish AnimatedTransitioningObject class, ViewController back again, ViewController to follow UINavigationBarDelegate and UIViewControllerTransitioningDelegate SecondVC transitioningDelegate set for yourself. Then return different animation implementations based on different operations.

@interface ViewController () <UINavigationControllerDelegate,UIViewControllerTransitioningDelegate>

- (IBAction)pushBtnClick:(id)sender
{
    SecondViewController * vc = [[SecondViewController alloc] init];
    vc.transitioningDelegate = self;
    [self.navigationController pushViewController:vc animated:YES];
}

#pragma mark - Push && Pop- (id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC  toViewController:(UIViewController *)toVC {if (operation == UINavigationControllerOperationPush)
    {
        return [TransitionAnimationObject initWithTransitionAnimationObjectType:TransitionAnimationObjectType_Push];
    }
    else if (operation == UINavigationControllerOperationPop)
    {
        return [TransitionAnimationObject initWithTransitionAnimationObjectType:TransitionAnimationObjectType_Pop];
    }
    return nil;
}
Copy the code

Let’s see what happens

Present animation and Dismiss animation

The Present implementation

- (void) presentAnimateTransition: (id < UIViewControllerContextTransitioning >) transitionContext {/ / acquisition target View (ThirdVC. View) And source View (ViewController. View) UIView * toView = [transitionContext viewForKey: UITransitionContextToViewKey]; UIView * fromView = [transitionContext viewForKey:UITransitionContextFromViewKey]; / / screenshots do animation UIView * tempView = [fromView snapshotViewAfterScreenUpdates: NO]; tempView.frame = fromView.frame; fromView.hidden = YES; UIView * containerView = [transitionContext containerView]; [containerView addSubview:tempView]; [containerView addSubview:toView]; CGFloat width = containerView.frame.size.width; CGFloat height = containerView.frame.size.height; Toview. frame = CGRectMake(0, height, width, 400); / / do animated transitions NSTimeInterval duration = [self transitionDuration: transitionContext]; [UIView animateWithDuration: duration delay: 0 usingSpringWithDamping: initialSpringVelocity 0.55:1 options: 0 animations: ^ { TempView. Transform = CGAffineTransformMakeScale (0.9, 0.9);  toView.transform = CGAffineTransformMakeTranslation(0, -400); } completion:^(BOOL finished) {// Complete the transition completeTransition:! [transitionContext transitionWasCancelled]];if([transitionContext transitionWasCancelled]) {fromView. Hidden = NO; [tempView removeFromSuperview]; }}]; }Copy the code

Dismiss implementation

- (void) dismissAnimateTransition: (id < UIViewControllerContextTransitioning >) transitionContext {/ / dismiss from time to time FromVC and toVC identity is inverted UIView * toView = [transitionContext viewForKey: UITransitionContextToViewKey]; UIView * fromView = [transitionContext viewForKey:UITransitionContextFromViewKey]; UIView * containerView = [transitionContext containerView]; UIView * tempView = [[containerView subviews] firstObject]; / / it is ok to do reduction animation NSTimeInterval duration = [self transitionDuration: transitionContext]; [UIView animateWithDuration: duration delay: 0 usingSpringWithDamping: initialSpringVelocity 0.55:1 options: 0 animations: ^ {  tempView.transform = CGAffineTransformIdentity; fromView.transform = CGAffineTransformIdentity; } completion:^(BOOL finished) {// Complete the transition completeTransition:! [transitionContext transitionWasCancelled]];if(! [transitionContext transitionWasCancelled]) {toview.hidden = NO; [tempView removeFromSuperview]; }}]; }Copy the code

UIViewControllerTransitioningDelegate proxy method

Go back to the ViewController, set ThirdVC’s transitioningDelegate to itself, and then customize the type in the proxy method.

- (IBAction)presentBtnClick:(id)sender
{
    ThirdViewController * vc = [[ThirdViewController alloc] init];
    vc.transitioningDelegate = self;
    [self presentViewController:vc animated:YES completion:nil];
}

#pragma mark - Present && Dismiss
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
    return [TransitionAnimationObject initWithTransitionAnimationObjectType:TransitionAnimationObjectType_present];
}

- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
    return [TransitionAnimationObject initWithTransitionAnimationObjectType:TransitionAnimationObjectType_Dismiss];
}
Copy the code

Gestures animation

UIPercentDrivenInteractiveTransition create gestures to class

Create a new gesture class GestureObject inherited from UIPercentDrivenInteractiveTransition, addGestureToViewController is add gestures to target controller.

#import <UIKit/UIKit.h>@interface GestureObject : UIPercentDrivenInteractiveTransition / / it is reciprocal gestures @ property (nonatomic, assign) BOOL interacting; - (void)addGestureToViewController:(UIViewController *)viewController; @endCopy the code

Then between the gesture states to determine whether to execute the animation, here is to judge the gesture offset more than half of the height of the screen to take effect, perform the relevant animation, otherwise restore the animation.

- (void)handleGesture:(UIPanGestureRecognizer *)ges
{
    CGPoint point = [ges translationInView:ges.view];

    switch (ges.state) {
        case UIGestureRecognizerStateBegan:
        {
            self.interacting = YES;
            [self.targetVC dismissViewControllerAnimated:YES completion:nil];
            break;
        }

        caseUIGestureRecognizerStateChanged: { CGFloat fraction = point.y / ges.view.frame.size.height; Fraction = MAX(0.0, MIN(1.0)); Self. ShouldComplete = fraction > 0.5; [self updateInteractiveTransition:fraction];break;
        }
        
        case UIGestureRecognizerStateEnded:
        case UIGestureRecognizerStateCancelled:
        {
            self.interacting = NO;
            if(! Self. ShouldComplete | | ges. State = = UIGestureRecognizerStateCancelled) {/ / reduction animation [self cancelInteractiveTransition]; }else{/ / [animating the self finishInteractiveTransition]; }break;
        }

        default:
            break; }}Copy the code

UIViewControllerTransitioningDelegate proxy method

Get back in the ViewController, at Present a ThirdVC add gestures, specified in the proxy method interactionControllerForDismissal gestures.

- (IBAction)presentBtnClick:(id)sender { ThirdViewController * vc = [[ThirdViewController alloc] init]; vc.transitioningDelegate = self; [self.gestureObject addGestureToViewController:vc]; [self presentViewController:vc animated:YES completion:nil]; } - (id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>) animator {return self.gestureObject.interacting ? self.gestureObject : nil;
}
Copy the code

See the effect

summary

Push, Pop, Present, Dismiss and gesture animation are all explained. It can be seen that the general steps of the custom transition are as follows

  • According to theviewForKeyTo get the transition context
  • The view to be transitioned is added to the transition container
  • Animate the transition
  • Mark the status of successful transition and do the corresponding processing according to the status

Understanding these, no matter how complex the transition animation can be decomposed step by step, the following is the transition effect of Guevara App, at the first time, I think it is cool, now understand the core of the transition, I think it is not so difficult, when you have time to write out its effect.

Source: TransitionAnimation