Animated transitions

A transition animation is a transition from one scene to another in the form of animation. The meaning of custom transition animation is to get rid of the fixed transition of the system and realize the transition animation with strong visual effect designed by UI interaction designers.

Below is the Demo menu screenshots of the case, in order to facilitate everybody step by step to master the custom transitions animation, every effect I wrote very detailed Demo (including navigation push transitions and modal modal transitions), recommend download following article first a case a case study to implement, to understand is helpful. Making address: github.com/Developer-L…










Effect display.gif

directory

0. CATransition (System transition animation)
I. Basic transition – non-interactive (a preliminary understanding of custom transition animation)
Two, imitation of cool dog transition – non-interactive (to consolidate the understanding of transition)
Iii. Imitation of wechat transition – non-interactive (to strengthen the understanding of transition)
Basic transitions – interactive
Five, actual combat – netizen Question One

Note: this article only explains some cases in the demo, but others are similar to these, it is good to expand, if you have questions, please leave a message

0. CATransition (System transition animation)

First of all, the easiest way to change the transition effect (not custom, but through the official animation effect implementation) is to provide the animation effect class is CATransition

CATransition

CATransition is a subclass of CAAnimation, which is used for transitions between pages. There are officially four public API animations, but the private API animations are much cooler (use private apis with caution).

(1) Nav transition: There is only one way to change the transition animation, which is very easy to understand: [the self navigationController. View. Layer addAnimation: [self pushAnimation] forKey: nil], (2) modal modal transitions: Similar to nav, [self.view.window.layer addAnimation:[self presentAnimation] forKey:nil]; This means that you add a CAAnimation class to the layer of the view, and the layer executes the animation provided by this class, so the transition animation is changed. CAAnimation subclass CATransition can quickly create animation effects, the following code is to change the system transition animation concrete implementation

- (void)pushSecond{ LYCATransitionSecondVC *second = [[LYCATransitionSecondVC alloc] init]; [self.navigationController.view.layer addAnimation:[self pushAnimation] forKey:nil]; / / add Animation [self navigationController pushViewController: second animated: NO]; / / remember the animated here should be set to NO, or you will repeat / * modal modal LYModalCATransitionSecondVC * second = [[LYModalCATransitionSecondVC alloc] init]; [self.view.window.layer addAnimation:[self presentAnimation] forKey:nil]; / / add Animation [self presentViewController: second animated: NO completion: nil]; } - (CATransition *)pushAnimation{CATransition* transition = [CATransition animation]; The transition. Duration = 0.8; transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; /* Private API Cube Effect pageCurl Page Up pageUnCurl Page down rippleEffect Water drop rippleEffect suckEffect becomes a small piece of cloth flying away CameraIrisHollowClose camera lens shutoff cameraIrisHollowOpen camera open effect. * / transition type = @ "cube"; //transition.type = kCATransitionMoveIn; KCATransitionMoveIn, kCATransitionPush, kCATransitionReveal, kCATransitionFade transition.subtype = kCATransitionFromRight; //kCATransitionFromLeft, kCATransitionFromRight, kCATransitionFromTop, kCATransitionFromBottom return transition; }Copy the code

See if it’s easy, try it yourself. Next is the real custom transition animation learning.

The real custom transition starts here

Starting with iOS7, apple provided an API for truly customizing transitions, which allowed us to define our own transitions for our apps. Transitions are non-interactive and interactive transitions, and here of course we start with the basic non-interactive transitions.

In fact, navigation and the realization of the modal modal custom transitions push, just the difference between a protocol, realize push class to follow UINavigationControllerDelegate agreement; Implementation of the modal class to follow UIViewControllerTransitioningDelegate agreement. The methods in both protocols are pretty much the same, so this series of articles will focus on case implementations in push transitions. (If you don’t know what these two protocols are, don’t worry, they will be explained in a minute.)

I. Basic transition – non-interactive (a preliminary understanding of custom transition animation)

This example is a simple two-step process to complete. Be patient and watch carefully, and you’ll see how easy it is to customize transitions too!

1. The followingUINavigationControllerDelegateProtocol, set the proxy.

In the Nav – BaseTransition Demo case, for example, in this class LYNavBaseVC oneself follow UINavigationControllerDelegate this agreement, In push transitions set before agent self. The navigationController. Delegate = self, and then realize the protocol specific methods, when push operation to perform, agent will callback implementations method, Proxy approach will require return followed UIViewControllerAnimatedTransitioning proxy objects of the agreement, to perform the corresponding animation. The LYNavBaseCustomAnimator (code is followed UIViewControllerAnimatedTransitioning class of the agreement, the agreement is a special offer and execute transitions in transitions animation, later will detail in section 2)

- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                   animationControllerForOperation:(UINavigationControllerOperation)operation
                                                fromViewController:(UIViewController *)fromVC
                                                  toViewController:(UIViewController *)toVC;Copy the code

Concrete code implementation

- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                            animationControllerForOperation:(UINavigationControllerOperation)operation
                                                         fromViewController:(UIViewController *)fromVC
                                                           toViewController:(UIViewController *)toVC{
    if (operation == UINavigationControllerOperationPush) {
        return self.customAnimator;

    }else if (operation == UINavigationControllerOperationPop){
        return self.customAnimator;
    }
    return nil;
}

- (LYNavBaseCustomAnimator *)customAnimator
{
    if (_customAnimator == nil) {
        _customAnimator = [[LYNavBaseCustomAnimator alloc]init];
    }
    return _customAnimator;
}Copy the code
2. Create an actor that provides the animation

The LYNavBaseCustomAnimator in the above code is the executor of the animation effect. It is to follow the class UIViewControllerAnimatedTransitioning agreement.

UIViewControllerAnimatedTransitioning agreement, the agreement is a ferry animation, animation effects of practitioners, the class is responsible for implement this agreement’s ability to provide all kinds of complex animation effects. There are two methods in the protocol that must be implemented

// this method controls the transitionDuration - (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext; // This is the transition context, which provides specific information about the two controllers during the transition. - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext; `Copy the code

Concrete code implementation

- (NSTimeInterval) transitionDuration: (nullable id < UIViewControllerContextTransitioning >) transitionContext {return 0.5; } - (void) animateTransition: (id < UIViewControllerContextTransitioning >) transitionContext {/ / ferry transition container view UIView *containerView = [transitionContext containerView]; //FromVC UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; UIView *fromView = fromViewController.view; fromView.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight); //ToVC UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIView *toView = toViewController.view; toView.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight); // This is push, Or pop operation BOOL isPush = ([toViewController. NavigationController. ViewControllers indexOfObject: toViewController] > [fromViewController.navigationController.viewControllers indexOfObject:fromViewController]); if (isPush) { [containerView addSubview:fromView]; [containerView addSubview:toView]; Toview. frame = CGRectMake(kScreenWidth, kScreenHeight, kScreenWidth, kScreenHeight); }else{ [containerView addSubview:toView]; [containerView addSubview:fromView]; Frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight); } // Since secondVC's view is above firstVC's view, So be added to the containerView / / animation after [UIView animateWithDuration: [self transitionDuration: transitionContext] animations: ^ {the if (isPush) { toView.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight);  }else{ fromView.frame = CGRectMake(kScreenWidth, kScreenHeight, kScreenWidth, kScreenHeight);  } } completion:^(BOOL finished) { BOOL wasCancelled = [transitionContext transitionWasCancelled]; // Set transitionContext to notify the system that the animation has been cancelled. [transitionContext completeTransition:! WasCancelled];}]; }Copy the code

Here’s a quick introduction to fromView and toView, otherwise it might get a little confusing

A push - > B B pop - > | | | | fromView toView who initiate in the ferry ferry, who is fromVC, fromView active push A to B, A is fromVC active pop into A, B B is fromVCCopy the code

As can be seen from the code, the transition animation customization is the fromView and toView operations, and these two views can be obtained in the context of this protocol, so it is not difficult to implement some simple custom transitions.

Case summary

For non-interactive transitions, in fact, only need to implement two protocols relevant methods: the first is UINavigationControllerDelegate, effect is compared to tell the system I have my own animated transitions, I want to go to my custom. The second is the UIViewControllerAnimatedTransitioning, I made the animation role is like, need you call directly.

Two, imitation of cool dog transition – non-interactive (to consolidate the understanding of transition)

Animation analysis:

First of all, we can understand that this animation is actually a linear animation, but it is curved, so it is ok to give the position of the start and end states. This animation can be implemented by CAAnimationGroup animation, but since the effect is not too smooth, I use the affine Transform CGAffineTransform. The following code

@implementation LYNavKuGouPushAnimator - (NSTimeInterval) transitionDuration: (id < UIViewControllerContextTransitioning >) transitionContext {return 0.4; } - (void) animateTransition: (id < UIViewControllerContextTransitioning >) transitionContext {/ / ferry transition container view UIView *containerView = [transitionContext containerView]; //ToVC UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIView *toView = toViewController.view; [containerView addSubview:toView]; Float centerX = toView.bounds.size. Width * 0.5; float centerX = toview.bounds.size. Float centerY. = toView bounds. Size. Height * 0.5; Float x = toview.bounds.size. Width * 0.5; float x = toview.bounds.size. Float y = toView. Bounds. Size. Height * 1.8; // Start state: Original state around the x, y rotated 45 state after the DHS CGAffineTransform trans = [self GetCGAffineTransformRotateAroundCenterX: centerX centerY: centerY x: x Y: y Angle: 45.0/180.0 * M_PI]; toView.transform = trans; [UIView animateWithDuration: [self transitionDuration: transitionContext] animations: ^ {/ / termination status: Original state toView. Transform = CGAffineTransformIdentity;  } completion:^(BOOL finished) { BOOL wasCancelled = [transitionContext transitionWasCancelled]; // Set transitionContext to notify the system that the animation has been cancelled. [transitionContext completeTransition:! WasCancelled];}]; } /** affine transform @param centerX view centerX coordinates @param centerY view centerY coordinates @param X rotation centerX coordinates @param Y rotation centerY coordinates @param Angle rotation Angle @ return CGAffineTransform object * / - (CGAffineTransform) GetCGAffineTransformRotateAroundCenterX centerX: (float) centerY:(float)centerY x:(float)x y:(float)y angle:(float)angle{ CGFloat l = y - centerY; CGFloat h = l * sin(angle); CGFloat b = l * cos(angle); CGFloat a = l - b; CGFloat x1 = h; CGFloat y1 = a; CGAffineTransform trans = CGAffineTransformMakeTranslation(x1, y1); trans = CGAffineTransformRotate(trans,angle); return trans; } @endCopy the code

It’s a little more cool to see the code for this class, because from the beginning of this case I have implemented the push and pop animators as a single class (LYNavKuGouPushAnimator and LYNavKuGouPopAnimator). Ideas that everyone understand more clear ~ (and since this case UINavigationControllerDelegate agreement also use a single class, LYNavKuGouAnimationTransition such as this case is to follow and implement the method of the protocol class, set in the controller of this class object for the broker)

Summary of case two

The essence of the transitions of animation is GetCGAffineTransformRotateAroundCenterX: centerY: x: y: Angle: method, this method can make according to the incoming parameters to calculate the view after the transformation of the location of the state. With the state before and after the CGAffineTransform, the animation effect can be achieved with a simple UIView animation.

Iii. Imitation of wechat transition – non-interactive (to strengthen the understanding of transition)

Animation analysis: Start with an example diagram





Example of wechat transfer. PNG

This animation is a little different from the first two animations, which animated the entire interface, but this animation only animates the zoomed image, and the background color is only gradient. Now that you know that you’re just going to animate an image, it’s not hard to imagine that you can add a UIImageView to containerView, and then you can animate it, and you can do that. Take a look at the code:

- (void) animateTransition: (id < UIViewControllerContextTransitioning >) transitionContext {/ / ferry transition container view UIView *containerView = [transitionContext containerView]; //FromVC UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; UIView *fromView = fromViewController.view; [containerView addSubview:fromView]; //ToVC UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIView *toView = toViewController.view; [containerView addSubview:toView]; toView.hidden = YES; // Blank view for the background of the image (set the same color as the background of the controller, Give a person a kind of the picture is transferred the illusion of [] can change kind of color to see see effect) UIView * imgBgWhiteView = [[UIView alloc] initWithFrame: self. TransitionBeforeImgFrame]; imgBgWhiteView.backgroundColor = bgColor; [containerView addSubview:imgBgWhiteView]; / / a gradient of black background UIView * bgView = [[UIView alloc] initWithFrame: containerView. Bounds]; bgView.backgroundColor = [UIColor blackColor]; bgView.alpha = 0; [containerView addSubview:bgView]; / images/transition UIImageView * transitionImgView = [[UIImageView alloc] initWithImage: self. TransitionImgView. Image]; transitionImgView.frame = self.transitionBeforeImgFrame; [transitionContext.containerView addSubview:transitionImgView]; [UIView animateWithDuration: [self transitionDuration: transitionContext] delay: usingSpringWithDamping 0.0:0.7 InitialSpringVelocity: 0.3 options: UIViewAnimationOptionCurveLinear animations: ^ {transitionImgView. Frame = self.transitionAfterImgFrame; bgView.alpha = 1; } completion:^(BOOL finished) { toView.hidden = NO;  [imgBgWhiteView removeFromSuperview]; [bgView removeFromSuperview]; [transitionImgView removeFromSuperview];  BOOL wasCancelled = [transitionContext transitionWasCancelled]; // Set transitionContext to notify the system that the animation has been cancelled. [transitionContext completeTransition:! WasCancelled];}]; }Copy the code

In the code, the blank view and the gradient black background play a supporting role, while the transition image is the core. To animate, you must have three pieces of data: the image image, the frame of the imageView before the transition, and the frame of the imageView after the transition. These three data are calculated from the first VC inside, just follow logical steps step by step to pass over. (1) When toView is added to containerView, it should be hidden until the end of the animation, otherwise the toView will cover up the fromView. (2) All views except fromViews and ToViews must be removed at the end of the animation, otherwise they will remain on containerView. (3) The fromView in popAnimator is no longer needed to be added to containerView, because this transition does not require fromView to be involved in pop.

Case three Summary:

1. Calculate the three necessary parameters of the Animator in VC and pass them to the Animator one by one. 2. Obtain the incoming data and animate the transition picture according to it

Basic transitions – interactive

Interactive transition: manually controlled transition, the most common interactive transition animation is the system’s own slide back.

Please refer to demo for this case

1. Implement proxy methods

In LYNavBaseInteractiveAnimatedTransition class, compared to the case of a UINavigationControllerDelegate agreement, to achieve a proxy methods, namely:

- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
                                   interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController;Copy the code

This method returns a followed UIViewControllerInteractiveTransitioning proxy objects of the agreement, implement this method, the system transitions, will know whether the current interactive transitions, transitions, it performs are no execute transitions of common custom animation. Specific code implementation:

- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{ if (operation == UINavigationControllerOperationPush) { return self.customAnimator; }else if (operation == UINavigationControllerOperationPop){ return self.customAnimator; } return nil; } - (LYNavBaseCustomAnimator *)customAnimator { if (_customAnimator == nil) { _customAnimator = [[LYNavBaseCustomAnimator alloc]init]; } return _customAnimator; } - (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController{ if (self.gestureRecognizer) return self.percentIntractive; else return nil; } - (void)setGestureRecognizer:(UIPanGestureRecognizer *)gestureRecognizer{ _gestureRecognizer = gestureRecognizer; } - (LYNavBasePercentDerivenInteractive *)percentIntractive{ if (! _percentIntractive) { _percentIntractive = [[LYNavBasePercentDerivenInteractive alloc] initWithGestureRecognizer:self.gestureRecognizer]; } return _percentIntractive; }Copy the code

GestureRecognizer is an interactive gesture added in secondVC, which needs to be transmitted during POP, which will be discussed later. (2) the class object is percentIntractive LYNavBasePercentDerivenInteractive, this class inherits from UIPercentDrivenInteractiveTransition class, UIPercentDrivenInteractiveTransition class is the core classes in interactive transitions, talk about later.

2. Create a file that inherits fromUIPercentDrivenInteractiveTransitionClass in the classLYNavBasePercentDerivenInteractive

UIPercentDrivenInteractiveTransition classes are system defined, it follows the UIViewControllerInteractiveTransitioning protocol, so it is a proxy objects in the first quarter. This class defines three more methods to be called during an interaction transition:

/ / update the percentage of the transitions process - (void) updateInteractiveTransition (CGFloat) percentComplete; / / cancel the transitions - (void) cancelInteractiveTransition; / / complete transitions - (void) finishInteractiveTransition;Copy the code

Specific code implementation:

- (void)gestureRecognizeDidUpdate:(UIPanGestureRecognizer *)gestureRecognizer { CGFloat scale = 1 - [self percentForGesture:gestureRecognizer]; The switch (gestureRecognizer. State) {case UIGestureRecognizerStateBegan: / / useless break; Case UIGestureRecognizerStateChanged/percentage/update: [self updateInteractiveTransition: scale]; break; Case UIGestureRecognizerStateEnded: if (scale < 0.3) {/ / cancel transitions [self cancelInteractiveTransition]; } else {/ / complete transitions [self finishInteractiveTransition]; } break; Default: / / cancel transitions [self cancelInteractiveTransition]; break; }}Copy the code

In this class, according to the gesture information transmitted during pop, the percentage of the screen to obtain the sliding distance is calculated, so as to process the cancellation and completion of transition according to the percentage.

3. The value

Here the value is different from before, the interaction here is interactive animation during POP, so the value is passed in SecondVC. Specific code:

- (void)interactiveTransitionRecognizerAction:(UIPanGestureRecognizer *)gestureRecognizer { CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view]; CGFloat scale = 1 - fabs(translation.x / kScreenWidth); scale = scale < 0 ? 0 : scale; NSLog(@"second = %f", scale); switch (gestureRecognizer.state) { case UIGestureRecognizerStatePossible: break; case UIGestureRecognizerStateBegan:{ //1. Set agent self.animatedTransition = nil; self.navigationController.delegate = self.animatedTransition; / / 2. The value of the self. AnimatedTransition. GestureRecognizer = gestureRecognizer; / / 3. Push the jump [self navigationController popViewControllerAnimated: YES]; } break; case UIGestureRecognizerStateChanged: { break; } case UIGestureRecognizerStateFailed: case UIGestureRecognizerStateCancelled: case UIGestureRecognizerStateEnded: { self.animatedTransition.gestureRecognizer = nil; }}}Copy the code

This case note: (1) the customAnimator LYNavBaseInteractiveAnimatedTransition class is directly in the case of a

Five, actual combat – netizen Question One

This case is the answer to the question on the second floor of this article

In fact, this case is basically the same as case 3. From the perspective of transition, there are two differences: (1) The frame of the ImageView before the transition is variable. In Case 3, there is only one frame of the ImageView. (2) The position of the ImageView after the transition is different from that of Case 3. Look at the code

- (CGRect)getFrameInWindow:(UIView *)view {return [view.superView convertRect:view.frame toView:nil]; }Copy the code

This method can solve the problem in (1). When clicking the cell, the UIImageView object on the cell is passed in and the frame of the View on the window is returned. In this way, the transition ImageView in the transition can be set according to the frame before the transition

- (CGRect)backScreenImageViewRectWithImage:(UIImage *)image{ CGSize size = image.size; CGSize newSize; Newsize. height = kScreenWidth * 0.6; newSize.width = newSize.height / size.height * size.width; CGFloat imageY = 0; CGFloat imageX = (kScreenWidth-newsie.width) * 0.5; CGRect rect = CGRectMake(imageX, imageY, newSize.width, newSize.height); return rect; }Copy the code

This method can solve the problem in (2). By importing image, we can calculate the position of the image after the transition according to our own needs.

— — — — — — — — — — — — — — — — — — — — end — — — — — — — — — — — — — — — — — — — —

Update log:

2017.07.05 (1) Updated Case 0 – CATransition (2) Updated Case 1 – Basic transition – Non-interaction (3) New Case 2 – Imitation cool dog transition – non-interaction (4) New case 3 – Imitation wechat transition – non-interaction (5) New case 4 – Basic transition – interactive (6) New case five – actual combat – netizen Question One

If this article has been helpful to you, please give me a thumbs up! After all, the code word is not easy

Github:github.com/Developer-L…