There are roughly three types of switching between view controllers

  • UITabBarController Switches its subview when its subview controller is selected
  • UINavigationController pushes/pops a subview
  • A UIViewController presented/ Presented Early

Custom non-interactive transition animation

General part 1, in order to realize the custom transition animations ViewController implementation of corresponding delegate, UITabBarViewController corresponding is UITabBarControllerDelegate, UINavigationController is corresponding UINavigationControllerDelegate. 2, at the beginning of the animation, the delegate calls the corresponding animation controller, which is realized UIViewControllerAnimatedTransitioning protocol classes, this class can be any type. If nil is returned, the default animation is used. 3, animation controller to achieve the corresponding method

  • TransitionDuration: animation execution duration
  • AnimateTransition: A place to implement custom animations

To implement a simple example in the code comment, change the UITabBarController view switch mode to left/right movement mode Swift version: This is a simple example, so create a Tabbled Application template for the project to implement the AppDelegate

import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { // Override point for customization after application launch. (self.window! .rootViewController as! UITabBarController).delegate = self return true } } extension AppDelegate : UITabBarControllerDelegate{ func tabBarController(tabBarController: UITabBarController, animationControllerForTransitionFromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { return self }} extension AppDelegate : UIViewControllerAnimatedTransitioning {/ / the execution time of the animation, Func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) - > NSTimeInterval 0.4} {return func animateTransition (transitionContext: UIViewControllerContextTransitioning) { let vc1 = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)! let vc2 = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! let containerView = transitionContext.containerView()! / / this method is iOS8 before some let the view1. = transitionContext viewForKey (UITransitionContextFromViewKey)! let view2 = transitionContext.viewForKey(UITransitionContextToViewKey)! // let view11 = vc1. View // let view22 = vc2 transitionContext.initialFrameForViewController(vc1) let r2end = transitionContext.finalFrameForViewController(vc2) //which key we are going depends on which vc is which the most general way to express this is in terms of index number let tab = self.window! .rootViewController as! UITabBarController let index1 = tab.viewControllers! .indexOf(vc1) let index2 = tab.viewControllers! .indexOf(vc2) let dir : CGFloat = index1 < index2 ? 1: -1 var r1end = r1start r1end.origin.x -= r1end.width * dir var r2start = r2end r2start.origin.x += r2start.width * dir View2.frame = r2start containerView.addSubView (view2) // Avoid interference from other gesture events UIApplication. SharedApplication (.) beginIgnoringInteractionEvents () / / perform specific animation, Different effects of the code is in the UIView. AnimateWithDuration (0.4, animations: {the view1. Frame = r1end view2. Frame = r2end}) {(_) in / / prevent cancel state transitionContext.com pleteTransition (! transitionContext.transitionWasCancelled()) UIApplication.sharedApplication().endIgnoringInteractionEvents() } } func animationEnded(transitionCompleted: Bool) { if transitionCompleted { print("Completed") }else{ print("NO Completed") } } }Copy the code

To keep the code a little cleaner and to make the animation a little easier to use, we’ll separate it out.

import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { // Override point for customization after application launch. (self.window! .rootViewController as! UITabBarController).delegate = self return true }Copy the code

}

extension AppDelegate : UITabBarControllerDelegate{ func tabBarController(tabBarController: UITabBarController, animationControllerForTransitionFromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { let animation = CustomTransitionAimation() animation.tab = (self.window! .rootViewController as! UITabBarController) return animation } }Copy the code

The implementation of animation classes

import UIKit class CustomTransitionAimation: NSObject,UIViewControllerAnimatedTransitioning { var tab : UITabBarController? AnimateTransition: func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {return 0.4} func animateTransition(transitionContext: UIViewControllerContextTransitioning) { let vc1 = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)! let vc2 = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! let containerView = transitionContext.containerView()! / / this method is iOS8 before some let the view1. = transitionContext viewForKey (UITransitionContextFromViewKey)! let view2 = transitionContext.viewForKey(UITransitionContextToViewKey)! // let view11 = vc1. View // let view22 = vc2 transitionContext.initialFrameForViewController(vc1) let r2end = transitionContext.finalFrameForViewController(vc2) //which key we are going depends on which vc is which the most general way to express this is in terms of index number let tab = self.tab! let index1 = tab.viewControllers! .indexOf(vc1) let index2 = tab.viewControllers! .indexOf(vc2) let dir : CGFloat = index1 < index2 ? 1: -1 var r1end = r1start r1end.origin.x -= r1end.width * dir var r2start = r2end r2start.origin.x += r2start.width * dir View2.frame = r2start containerView.addSubView (view2) // Avoid interference from other gesture events UIApplication. SharedApplication (.) beginIgnoringInteractionEvents () / / perform specific animation, Different effects of the code is in the UIView. AnimateWithDuration (0.4, animations: {the view1. Frame = r1end view2. Frame = r2end}) {(_) in / / prevent cancel state transitionContext.com pleteTransition (! transitionContext.transitionWasCancelled()) UIApplication.sharedApplication().endIgnoringInteractionEvents() } } func animationEnded(transitionCompleted: Bool) { if transitionCompleted { print("Completed") }else{ print("NO Completed") } } }Copy the code

Objective – C version

#import "AppDelegate.h" #import "CusomTransitionAnimation.h" @interface AppDelegate ()<UITabBarControllerDelegate> @property(nonatomic,strong) UITabBarController *tabBarController; @property(nonatomic,strong) CusomTransitionAnimation *customAnimation; @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. self.tabBarController = (UITabBarController *)self.window.rootViewController; self.tabBarController.delegate = self; return YES; } - (id <UIViewControllerAnimatedTransitioning> )tabBarController:(UITabBarController *)tabBarController animationControllerForTransitionFromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC { CusomTransitionAnimation *animation = [[CusomTransitionAnimation alloc]init]; animation.tabBarController = self.tabBarController; return animation; } - (CusomTransitionAnimation *)customAnimation { if (! _customAnimation) { _customAnimation = [[CusomTransitionAnimation alloc]init]; _customAnimation.tabBarController = self.tabBarController; } return _customAnimation; } @endCopy the code

Animation class

//.h #import <Foundation/Foundation.h> #import <UIKit/UIKit.h> @interface CusomTransitionAnimation : NSObject<UIViewControllerAnimatedTransitioning> @property(nonatomic,strong) UITabBarController *tabBarController; @end //.m #import "CusomTransitionAnimation.h" @implementation CusomTransitionAnimation - (NSTimeInterval) transitionDuration: (nullable id < UIViewControllerContextTransitioning >) transitionContext {return 0.4; } - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext { UIView *containerView = [transitionContext containerView]; UIViewController *vc1 = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; UIViewController *vc2 = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIView *view1 = [transitionContext viewForKey:UITransitionContextFromViewKey]; UIView *view2 = [transitionContext viewForKey:UITransitionContextToViewKey]; CGRect vc1start = [transitionContext initialFrameForViewController:vc1]; CGRect vc2end = [transitionContext finalFrameForViewController:vc2]; NSUInteger index1 = [self.tabBarController.viewControllers indexOfObject:vc1]; NSUInteger index2 = [self.tabBarController.viewControllers indexOfObject:vc2]; int dir = index1 < index2 ? 1:1; CGRect vc1end = vc1start; vc1end.origin.x -= vc1end.size.width * dir; CGRect vc2start = vc2end; vc2start.origin.x += vc2start.size.width * dir; view2.frame = vc2start; [containerView addSubview:view2]; [[UIApplication sharedApplication]beginIgnoringInteractionEvents]; [UIView animateWithDuration:0.4 animations:^{view1.frame = vc1end; view2.frame = vc2end; view2.animateWithDuration :0.4 animations:^{view1.frame = vc1end; view2.frame = vc2end;  } completion:^(BOOL finished) { [transitionContext completeTransition:(! transitionContext.transitionWasCancelled)]; [[UIApplication sharedApplication]endIgnoringInteractionEvents]; }]; } - (void)animationEnded:(BOOL)transitionCompleted { if (transitionCompleted) { NSLog(@"Completed"); }else{ NSLog(@"No Completed"); } } @endCopy the code

The UINavigationController implementation is similar, except that the animation class is called differently

ViewController implementation

import UIKit

class ViewController: UIViewController {

override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. self.navigationController! .delegate = self } @IBAction func pushAction(sender: AnyObject) { let second = SecondViewController() self.navigationController! .pushViewController(second, animated: true) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } extension ViewController : UINavigationControllerDelegate{ func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {/ / operation is used to distinguish a push or pop operation if the operation = =. Push {return CustomPushTransitionAimation ()} else if operation = = .Pop{ return CustomPopTransitionAimation() } return nil } }Copy the code

Push the animation

import UIKit class CustomPushTransitionAimation: NSObject, UIViewControllerAnimatedTransitioning {/ / the execution time of the animation, Func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {return 0.4} func animateTransition(transitionContext: UIViewControllerContextTransitioning) { // let vc1 = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)! // let vc2 = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! let containerView = transitionContext.containerView()! / / this method is iOS8 before some let the view1. = transitionContext viewForKey (UITransitionContextFromViewKey)! let view2 = transitionContext.viewForKey(UITransitionContextToViewKey)! / / / / before iOS8 way let view11 ='s vc1. View / / let view22 = vc2. View view2. Transform = CGAffineTransformMakeScale (0.1, 0.1) containerView. AddSubview (view2) / / avoid the interference of other gesture events UIApplication sharedApplication () beginIgnoringInteractionEvents () / / perform specific animation, the effect of different code is different in that the UIView. AnimateWithDuration (0.4, animations: {{view2. Transform = CGAffineTransformIdentity}) (_) in / / prevent cancel state transitionContext.com pleteTransition (! transitionContext.transitionWasCancelled()) UIApplication.sharedApplication().endIgnoringInteractionEvents() } } func animationEnded(transitionCompleted: Bool) { if transitionCompleted { print("Completed") }else{ print("NO Completed") } }Copy the code

}

Pop animation

import UIKit class CustomPushTransitionAimation: NSObject, UIViewControllerAnimatedTransitioning {/ / the execution time of the animation, Func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {return 0.4} func animateTransition(transitionContext: UIViewControllerContextTransitioning) { // let vc1 = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)! // let vc2 = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! let containerView = transitionContext.containerView()! / / this method is iOS8 before some let the view1. = transitionContext viewForKey (UITransitionContextFromViewKey)! let view2 = transitionContext.viewForKey(UITransitionContextToViewKey)! / / / / before iOS8 way let view11 ='s vc1. View / / let view22 = vc2. View view2. Transform = CGAffineTransformMakeScale (0.1, 0.1) containerView. AddSubview (view2) / / avoid the interference of other gesture events UIApplication sharedApplication () beginIgnoringInteractionEvents () / / perform specific animation, the effect of different code is different in that the UIView. AnimateWithDuration (0.4, animations: {{view2. Transform = CGAffineTransformIdentity}) (_) in / / prevent cancel state transitionContext.com pleteTransition (! transitionContext.transitionWasCancelled()) UIApplication.sharedApplication().endIgnoringInteractionEvents() } } func animationEnded(transitionCompleted: Bool) { if transitionCompleted { print("Completed") }else{ print("NO Completed") } } }Copy the code

Interactive custom transition animation

There are two ways to do this

Using percentage drivers

It is easier to use this method, but first implement non-interactive custom animations as described above. Next, implement the callback method

func tabBarController(tabBarController: UITabBarController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
Copy the code

Mainly UIPercentDrivenInteractiveTransition use, essence is frozen animation.

import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? var rightEdgr : UIScreenEdgePanGestureRecognizer! var leftEdgr : UIScreenEdgePanGestureRecognizer! var inter : UIPercentDrivenInteractiveTransition! // This class is the key. UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { // Override point for customization after application launch. let tab = (self.window! .rootViewController as! UITabBarController) tab.delegate = self let sep = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(pan)) sep.edges = .Right sep.delegate = self tab.view.addGestureRecognizer(sep) self.rightEdgr = sep let sep2 = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(pan)) sep2.edges = .Left sep2.delegate = self tab.view.addGestureRecognizer(sep2) self.leftEdgr = sep2 return true }Copy the code

}

extension AppDelegate : UITabBarControllerDelegate{ func tabBarController(tabBarController: UITabBarController, animationControllerForTransitionFromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { // let animation = CustomTransitionAimation() // animation.tab = (self.window! .rootViewController as! UITabBarController) // return animation return self } func tabBarController(tabBarController: UITabBarController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { if self.interacting { return self.inter } return nil } } extension AppDelegate : UIGestureRecognizerDelegate{ func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool { let tab = self.window! .rootViewController as! UITabBarController var result = false if gestureRecognizer == self.rightEdgr { result = (tab.selectedIndex < tab.viewControllers! .count - 1) } else{ result = tab.selectedIndex > 0 } return result } func pan(g : UIScreenEdgePanGestureRecognizer) - > Void {/ / key need to handle the let v = g.v iew! let tab = self.window! .rootViewController as! UITabBarController let delta = g.translationInView(v) let percent = fabs(delta.x/v.bounds.width) switch g.state { case .Began: self.inter = UIPercentDrivenInteractiveTransition() self.interacting = true if g == self.rightEdgr { tab.selectedIndex =  tab.selectedIndex + 1 }else{ tab.selectedIndex = tab.selectedIndex - 1 } print("selectedIndex = \(tab.selectedIndex)") case .Changed: self.inter.updateInteractiveTransition(percent) case .Ended: If percent > {0.5 self. Intel. FinishInteractiveTransition () / / call the method, Will quickly before the end of the set of operations} else {self. Intel. CancelInteractiveTransition () / / call the method, will recover} the self. The interacting = false case. The Cancelled: self.inter.cancelInteractiveTransition() self.interacting = false default : break } } } extension AppDelegate : UIViewControllerAnimatedTransitioning {/ / the execution time of the animation, Func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) - > NSTimeInterval 0.4} {return func animateTransition (transitionContext: UIViewControllerContextTransitioning) { let vc1 = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)! let vc2 = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! let containerView = transitionContext.containerView()! / / this method is iOS8 before some let the view1. = transitionContext viewForKey (UITransitionContextFromViewKey)! let view2 = transitionContext.viewForKey(UITransitionContextToViewKey)! // let view11 = vc1. View // let view22 = vc2 transitionContext.initialFrameForViewController(vc1) let r2end = transitionContext.finalFrameForViewController(vc2) //which key we are going depends on which vc is which the most general way to express this is in terms of index number let tab = self.window! .rootViewController as! UITabBarController let index1 = tab.viewControllers! .indexOf(vc1) let index2 = tab.viewControllers! .indexOf(vc2) let dir : CGFloat = index1 < index2 ? 1: -1 var r1end = r1start r1end.origin.x -= r1end.width * dir var r2start = r2end r2start.origin.x += r2start.width * dir View2.frame = r2start containerView.addSubView (view2) /* // Avoid interference from other gesture events UIApplication. SharedApplication (.) beginIgnoringInteractionEvents () / / perform specific animation, Different effects of the code is in the UIView. AnimateWithDuration (0.4, animations: {the view1. Frame = r1end view2. Frame = r2end}) {(_) in / / prevent cancel state transitionContext.com pleteTransition (! transitionContext.transitionWasCancelled()) UIApplication.sharedApplication().endIgnoringInteractionEvents() } */ let opts : UIViewAnimationOptions = self.interacting ? .CurveLinear : [] if ! The self. The interacting {UIApplication. SharedApplication () beginIgnoringInteractionEvents ()} UIView. AnimateWithDuration (0.4, delay: 0, options: opts, animations: { view1.frame = r1end view2.frame = r2end }) { (_) in let canceld = transitionContext.transitionWasCancelled() transitionContext.completeTransition(! canceld) if UIApplication.sharedApplication().isIgnoringInteractionEvents(){ UIApplication.sharedApplication().endIgnoringInteractionEvents() } } } func animationEnded(transitionCompleted: Bool) { if transitionCompleted { print("Completed") }else{ print("NO Completed") } let tab = self.window! .rootViewController as! UITabBarController print(tab.selectedIndex) } }Copy the code
Do not use percentage drivers

This may seem a little more complicated, but it’s more customizable because you can do things like fromView and toView directly.

The animateTransition method no longer needs to be implemented specifically, but it still needs to exist because its protocol must implement the method.

func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
    
}
Copy the code

Put the code in implementing UIViewControllerInteractiveTransitioning class to implement the agreement. On the way

func startInteractiveTransition(transitionContext: UIViewControllerContextTransitioning){
}
Copy the code

Medium will do.

This time use OC implementation, all familiar with:

#import "AppDelegate.h"

@interface AppDelegate ()<UIViewControllerAnimatedTransitioning,UIViewControllerInteractiveTransitioning,UITabBarControllerDelegate,UIGestureRecognizerDelegate>

@property(nonatomic,strong) id<UIViewControllerContextTransitioning>transitionContext;
@property(nonatomic,strong) UIScreenEdgePanGestureRecognizer *leftEdgr;
@property(nonatomic,strong) UIScreenEdgePanGestureRecognizer *rightEdgr;
@property(nonatomic,assign) BOOL interacting;
@property(nonatomic,assign) CGRect r1end;
@property(nonatomic,assign) CGRect r2start;

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.

UITabBarController *tab = (UITabBarController *)self.window.rootViewController;
tab.delegate = self;
self.leftEdgr = ({
    UIScreenEdgePanGestureRecognizer *pan = [[UIScreenEdgePanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];
    pan.edges = UIRectEdgeLeft;
    pan.delegate = self;
    [tab.view addGestureRecognizer:pan];
    pan;
});
self.rightEdgr = ({
    UIScreenEdgePanGestureRecognizer *pan = [[UIScreenEdgePanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];
    pan.edges = UIRectEdgeRight;
    pan.delegate = self;
    [tab.view addGestureRecognizer:pan];
    pan;
});

self.r1end = CGRectZero;
self.r2start = CGRectZero;

return YES;
}

#pragma mark - Actions

- (void)pan:(UIScreenEdgePanGestureRecognizer *)ges
{
UIView *gesView = ges.view;
UITabBarController *tab = (UITabBarController *)self.window.rootViewController;
CGPoint delta = [ges translationInView:gesView];
CGFloat percent = delta.x/gesView.bounds.size.width;

UIViewController *fromVC,*toVC;
UIView *fromView,*toView;
CGRect fromRectStart,toRectEnd;

if (self.transitionContext) {
    fromVC = [self.transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    toVC = [self.transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
    fromView = [self.transitionContext viewForKey:UITransitionContextFromViewKey];
    toView = [self.transitionContext viewForKey:UITransitionContextToViewKey];
    
    fromRectStart = [self.transitionContext initialFrameForViewController:fromVC];
    toRectEnd = [self.transitionContext finalFrameForViewController:toVC];
}

switch (ges.state) {
    case UIGestureRecognizerStateBegan: {
        self.interacting = YES;
        if (ges == self.leftEdgr) {
            tab.selectedIndex = tab.selectedIndex - 1;
        }else{
            tab.selectedIndex = tab.selectedIndex + 1;
        }
        break;
    }
    case UIGestureRecognizerStateChanged:{
        //可以在这里对fromView 和 toView 进行直接操作,所以,可定制程度高
        
        fromRectStart.origin.x += (self.r1end.origin.x - fromRectStart.origin.x) * percent;
        fromView.frame = fromRectStart;
        NSLog(@"fromRect : %@",NSStringFromCGRect(fromRectStart));
        CGRect toRectStart = self.r2start;
        toRectStart.origin.x += (toRectStart.origin.x - self.r2start.origin.x) * percent;
        toView.frame = toRectStart;
        NSLog(@"toRect : %@",NSStringFromCGRect(toRectStart));
        //在UITabBarController的切换中可能看不出来这句话有什么用处,但是在UINavigationController中这个就很有用了,可以自己处理NavigationBar的效果等。
        [self.transitionContext updateInteractiveTransition:percent];
        break;
    }
    case UIGestureRecognizerStateEnded: {
        if (percent > 0.5) {
            //达到要求,设置成最终状态
            [UIView animateWithDuration:0.2 animations:^{
                fromView.frame = self.r1end;
                toView.frame = toRectEnd;
            } completion:^(BOOL finished) {
                [self.transitionContext finishInteractiveTransition];
                [self.transitionContext completeTransition:YES];
            }];
        }
        else{
            //恢复原状
            [UIView animateWithDuration:0.2 animations:^{
                fromView.frame = fromRectStart;
                toView.frame = self.r2start;
            } completion:^(BOOL finished) {
                [self.transitionContext cancelInteractiveTransition];
                [self.transitionContext completeTransition:NO];
            }];
        }
        
        self.interacting = NO;
        self.transitionContext = nil;
        
        break;
    }
    case UIGestureRecognizerStateCancelled: {
        
        //恢复原状
        fromView.frame = fromRectStart;
        toView.frame = self.r2start;
        
        [self.transitionContext finishInteractiveTransition];
        [self.transitionContext completeTransition:NO];
        
        self.interacting = NO;
        self.transitionContext = nil;
        
        break;
    }
        default:
        break;
}}

#pragma mark - UIGestureRecognizerDelegate

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
UITabBarController *tab = (UITabBarController *)self.window.rootViewController;
BOOL result = NO;

if (gestureRecognizer == self.leftEdgr) {
    result = tab.selectedIndex > 0;
}else{
    result = tab.selectedIndex < tab.viewControllers.count - 1;
}

return result;
}

#pragma mark - UITabBarControllerDelegate

- (nullable id <UIViewControllerInteractiveTransitioning>)tabBarController:(UITabBarController *)tabBarController
                           interactionControllerForAnimationController: (id <UIViewControllerAnimatedTransitioning>)animationController
{
return self.interacting ? self : nil;
}

- (nullable id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController
                 animationControllerForTransitionFromViewController:(UIViewController *)fromVC
                                                   toViewController:(UIViewController *)toVC
{
return self.interacting ? self : nil;
}

#pragma mark - UIViewControllerAnimatedTransitioning

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

//这个要实现,但是要保持为空
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{

}

#pragma mark - UIViewControllerInteractiveTransitioning
//这个其实就是要实现 animateTransition: 原来实现的功能
- (void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{

self.transitionContext = transitionContext;

//即最终要达到的状态
UIView *containerView = [transitionContext containerView];

UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

//    UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];

CGRect fromRectStart = [transitionContext initialFrameForViewController:fromVC];
CGRect toRectEnd = [transitionContext finalFrameForViewController:toVC];

UITabBarController *tab = (UITabBarController *)self.window.rootViewController;
NSUInteger fromIndex = [tab.viewControllers indexOfObject:fromVC];
NSUInteger toIndex = [tab.viewControllers indexOfObject:toVC];
int dir = fromIndex < toIndex ? 1 : -1;

CGRect fromRectEnd = fromRectStart;
fromRectEnd.origin.x -= fromRectEnd.size.width * dir;

CGRect toRectStart = toRectEnd;
toRectStart.origin.x += toRectStart.size.width * dir;
toView.frame = toRectStart;
[containerView addSubview:toView];

self.r1end = fromRectEnd;
self.r2start = toRectStart;
}

@end
Copy the code

Custom Chicago ViewController transition animation

There are two main ways to implement this

  • Presentation Controller is not used
  • Use the presentation controller
Presentation Controller is not used

Here’s the caveat

  • The position of the protocol transitioningDelegate declaration
  • Distinguish between present and dismiss
  • Only if modalPresentationStyle * is not.fullscreen * can you use the following method to determine present or dismiss

In the code

import UIKit class ViewController2: UIViewController { override init(nibName nibNameOrNil: String? , bundle nibBundleOrNil: NSBundle?) { super.init(nibName: nibNameOrNil, bundle: NibBundleOrNil) / / this can no longer perform in viewDidLoad, it's too late to the self. The transitioningDelegate = self} required init? (coder aDecoder: NSCoder) { super.init(coder: aDecoder) self.transitioningDelegate = self } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. self.view.backgroundColor = UIColor.blueColor() } override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) { self.presentingViewController! .dismissViewControllerAnimated(true, completion: nil) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be  recreated. } } extension ViewController2 : UIViewControllerTransitioningDelegate{ func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return self } func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return self } } extension ViewController2 : UIViewControllerAnimatedTransitioning{ func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {return 0.4} func animateTransition(transitionContext: UIViewControllerContextTransitioning) { let containerView = transitionContext.containerView()! //For a presentation that is not .FullScreen,the unused view is nil. let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey) let toView = transitionContext.viewForKey(UITransitionContextToViewKey) //distinguish the cases //dismiss if fromView ! = nil {UIView. AnimateWithDuration (0.4, animations: {fromView! FromView. Transform = CGAffineTransformMakeScale (0.1, 0.1). .alpha = 0 }, completion: { (_) in transitionContext.completeTransition(! transitionContext.transitionWasCancelled()) }) } //presenting else if toView ! = nil{ let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)! let toRectEnd = transitionContext.finalFrameForViewController(toVC) toView! .frame = CGRectInset(toRectEnd, 40, 40) toView! ToView. Transform = CGAffineTransformMakeScale (0.1, 0.1). .alpha = 0 containerView.addSubview(toView!) //also can modify the containerView // containerView.backgroundColor = UIColor.greenColor() UIView. AnimateWithDuration (0.4, animations: {toView! .transform = CGAffineTransformIdentity toView! . Alpha = 1.0}) {(_) in transitionContext.com pleteTransition (! transitionContext.transitionWasCancelled()) } } } }Copy the code

In the presenting View of the.FullScreen type, it takes the presenting View and adds it to containerView, while in the presenting View of the.FullScreen type, containerView comes before the presenting View, You can’t animate the containerView, and it won’t remove it.

Use the Presentation Controller

A few things to understand before we do that

  • The Animation Controller is responsible for the Animation effect, i.e. how does the Chicago View move to its final destination
  • The Presentation Controller determines the final Chicago View location and adds views to the containerView, such as translucent effects. For example, if you just implement the UIPresentationController without doing the above work, the default animation will pop up.

Next, implementation: There are several things that need to be done to achieve the above effects

  • When you set the transitioningDelegate property of the Presented viewController, you also need to set its modalPresentationStyle to. Custom, this is required. (Note the code location)

    // NB if we want to modify the _animation_, we need to set the transitioningDelegate self.transitioningDelegate = self // if we want to modify the _presentation_, we need to set the style to custom // customize presentation only on iPhone // how will we find out which it is? we have no traitCollection yet... // I know, let's ask the window if UIApplication.sharedApplication().keyWindow! .traitCollection.userInterfaceIdiom == .Phone { self.modalPresentationStyle = .Custom }Copy the code
  • I’m implementing another method

    @ the available (iOS 8.0. *) optional public func presentationControllerForPresentedViewController (presented: UIViewController, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController?Copy the code

The next step is to override its methods in a subclass of UIPresentationController to achieve the desired effect

Method description:

// Position of the presented view in the container view by the end of the presentation transition.
// (Default: container view bounds)
public func frameOfPresentedViewInContainerView() -> CGRect
Copy the code

Return to the final Chicago View location

Use the following methods to add or remove additional views to containerView

presentationTransitionWillBegin
presentationTransitionDidEnd
dismissalTransitionWillBegin
dismissalTransitionDidEnd
Copy the code

Use the following method to lay out the additional views you add

containerViewWillLayoutSubviews
containerViewDidLayoutSubviews
Copy the code

methods

shouldPresentInFullscreen
Copy the code

The default returns true. If false, the presentation is changed to.currentContext

Those are the main ones. End.

Use of Transition coordinators

Focus on the use of a few methods

  • AnimateAlongsideTransition: completion: can be used to have on the view of child views to add animation effects

    - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; if (self.transitionCoordinator ! = nil) { [self.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) { NSLog(@"Transition Animation"); self.animationView.center = self.view.center;  } completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) { NSLog(@"Transition completion"); }]; } else{ NSLog(@"Transition is NULL"); }}Copy the code
  • notifyWhenInteractionEndsUsingBlock:

Check whether the gesture is successful when the monitoring view returns. For example in UINavigationController when you swipe back to the left, you need to do that in the previous ViewController.

- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (self.transitionCoordinator != nil) {
    
    [self.transitionCoordinator notifyWhenInteractionEndsUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
        if ([context isCancelled]) {
            NSLog(@"1 Interaction Cancelled");
            return ;
        }
        NSLog(@"1 Interaction Ends");
    }];
}
else{
    NSLog(@"1 Transition is NULL");
}
}Copy the code