自定义过渡动画

作者: 懒得起名的伊凡 | 来源:发表于2016-05-20 06:58 被阅读413次

    各视图控制器之间的切换,大概可分为三种

    • UITabBarController 他的子视图控制器被选中时,切换子视图
    • UINavigationController push/pop一个子视图时
    • 一个UIViewController 被presented/dismissed时

    自定义非交互式过渡动画

    通用部分
    1、所要实现自定义过渡动画的ViewController实现对应的delegate,UITabBarViewController对应的是UITabBarControllerDelegate,UINavigationController对应的是UINavigationControllerDelegate
    2、在动画开始时,delegate会调用对应的动画控制器,也就是实现了UIViewControllerAnimatedTransitioning协议的类,这个类可以是任意类型。返回nil,则使用默认动画效果。
    3、动画控制器实现对应的方法

    • transitionDuration:
      动画执行时间
    • animateTransition:
      实现自定义动画的地方

    其他解释在代码注释中
    实现一个简单的例子,将UITabBarController的切换视图方式改成左右移动的方式
    Swift版本:
    只是个简单的例子,所以建立一个 Tabbled Application 模板的项目
    实现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{
    
    //动画的执行时间,需要注意的是一定要和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()!
        
        //这个方法是iOS8之后才有的
        let view1 = transitionContext.viewForKey(UITransitionContextFromViewKey)!
        let view2 = transitionContext.viewForKey(UITransitionContextToViewKey)!
        
        //iOS8之前取得的方式
        //        let view11 = vc1.view
        //        let view22 = vc2.view
        
        
        let r1start = 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)
        
        //避免其他手势事件的干扰
        UIApplication.sharedApplication().beginIgnoringInteractionEvents()
        //执行具体动画,不同效果的代码不同处就是在这了
        UIView.animateWithDuration(0.4, animations: {
            view1.frame = r1end
            view2.frame = r2end
        }) { (_) in
            //防止取消状态
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
            UIApplication.sharedApplication().endIgnoringInteractionEvents()
        }
    }
    
    func animationEnded(transitionCompleted: Bool) {
        if transitionCompleted {
            print("Completed")
        }else{
            print("NO Completed")
        }
    }
    }
    

    为了让代码显得整洁一些,并且可以是该动画可以在多出更方便的使用,我们将其独立出来。

    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? {
        let animation = CustomTransitionAimation()
        animation.tab = (self.window!.rootViewController as! UITabBarController)
        return animation
    }
    }
    

    动画类的实现

    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()!
        
        //这个方法是iOS8之后才有的
        let view1 = transitionContext.viewForKey(UITransitionContextFromViewKey)!
        let view2 = transitionContext.viewForKey(UITransitionContextToViewKey)!
        
        //iOS8之前取得的方式
        //        let view11 = vc1.view
        //        let view22 = vc2.view
        
        let r1start = 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)
        
        //避免其他手势事件的干扰
        UIApplication.sharedApplication().beginIgnoringInteractionEvents()
        //执行具体动画,不同效果的代码不同处就是在这了
        UIView.animateWithDuration(0.4, animations: {
            view1.frame = r1end
            view2.frame = r2end
        }) { (_) in
            //防止取消状态
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
            UIApplication.sharedApplication().endIgnoringInteractionEvents()
        }
    }
    
    func animationEnded(transitionCompleted: Bool) {
        if transitionCompleted {
            print("Completed")
        }else{
            print("NO Completed")
        }
    }
    
    }
    

    Objective-C版本

    #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;
    }
    
    @end
    

    动画类

    //.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;
    } completion:^(BOOL finished) {
        [transitionContext completeTransition:(!transitionContext.transitionWasCancelled)];
        [[UIApplication sharedApplication]endIgnoringInteractionEvents];
    }];
    }
    
    - (void)animationEnded:(BOOL)transitionCompleted
    {
    if (transitionCompleted) {
        NSLog(@"Completed");
    }else{
        NSLog(@"No Completed");
    }
    }
    @end
    

    UINavigationController实现是类似的,只不过调用动画类的方式不同

    viewController实现

    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 用来区分是push还是pop操作
        if operation == .Push {
            return CustomPushTransitionAimation()
        }
        else if operation == .Pop{
            return CustomPopTransitionAimation()
        }
        return nil
    }
    }
    

    Push动画

    import UIKit
    
    class CustomPushTransitionAimation: NSObject,UIViewControllerAnimatedTransitioning {
    
    //动画的执行时间,需要注意的是一定要和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()!
        
        //这个方法是iOS8之后才有的
        let view1 = transitionContext.viewForKey(UITransitionContextFromViewKey)!
        let view2 = transitionContext.viewForKey(UITransitionContextToViewKey)!
        
        //iOS8之前取得的方式
        //        let view11 = vc1.view
        //        let view22 = vc2.view
    
        view2.transform = CGAffineTransformMakeScale(0.1, 0.1)
        containerView.addSubview(view2)
        
        //避免其他手势事件的干扰
        UIApplication.sharedApplication().beginIgnoringInteractionEvents()
        //执行具体动画,不同效果的代码不同处就是在这了
        UIView.animateWithDuration(0.4, animations: {
            view2.transform = CGAffineTransformIdentity
        }) { (_) in
            //防止取消状态
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
            UIApplication.sharedApplication().endIgnoringInteractionEvents()
        }
    }
    
    func animationEnded(transitionCompleted: Bool) {
        if transitionCompleted {
            print("Completed")
        }else{
            print("NO Completed")
        }
    }
    

    }

    Pop动画

    import UIKit
    
    class CustomPushTransitionAimation: NSObject,UIViewControllerAnimatedTransitioning {
    
    //动画的执行时间,需要注意的是一定要和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()!
        
        //这个方法是iOS8之后才有的
        let view1 = transitionContext.viewForKey(UITransitionContextFromViewKey)!
        let view2 = transitionContext.viewForKey(UITransitionContextToViewKey)!
        
        //iOS8之前取得的方式
        //        let view11 = vc1.view
        //        let view22 = vc2.view
    
        view2.transform = CGAffineTransformMakeScale(0.1, 0.1)
        containerView.addSubview(view2)
        
        //避免其他手势事件的干扰
        UIApplication.sharedApplication().beginIgnoringInteractionEvents()
        //执行具体动画,不同效果的代码不同处就是在这了
        UIView.animateWithDuration(0.4, animations: {
            view2.transform = CGAffineTransformIdentity
        }) { (_) in
            //防止取消状态
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
            UIApplication.sharedApplication().endIgnoringInteractionEvents()
        }
    }
    
    func animationEnded(transitionCompleted: Bool) {
        if transitionCompleted {
            print("Completed")
        }else{
            print("NO Completed")
        }
    }
    }
    

    可交互的自定义过渡动画

    有两种实现方式

    使用百分比驱动

    使用这种方式比较简单,还是按照上述的方法先实现不可交互的自定义动画。接下来实现回调方法

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

    主要是UIPercentDrivenInteractiveTransition的使用,实质是frozen animation。

    import UIKit
    
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
    
    var window: UIWindow?
    
    var rightEdgr : UIScreenEdgePanGestureRecognizer!
    var leftEdgr : UIScreenEdgePanGestureRecognizer!
    var inter : UIPercentDrivenInteractiveTransition!//这个类是关键
    var interacting = false
    
    func application(application: 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
    }
    

    }

    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 {
        //重点需要处理的地方
        let v = g.view!
        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.inter.finishInteractiveTransition()//调用该方法,将快速结束之前所设置的操作
            }else{
                self.inter.cancelInteractiveTransition()//调用该方法,将恢复原状
            }
            self.interacting = false
        case .Cancelled:
            self.inter.cancelInteractiveTransition()
            self.interacting = false
        default : break
        }
        
    }
    }
    
    
    
    extension AppDelegate : UIViewControllerAnimatedTransitioning{
    
    //动画的执行时间,需要注意的是一定要和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()!
        
        //这个方法是iOS8之后才有的
        let view1 = transitionContext.viewForKey(UITransitionContextFromViewKey)!
        let view2 = transitionContext.viewForKey(UITransitionContextToViewKey)!
        
        //iOS8之前取得的方式
        //        let view11 = vc1.view
        //        let view22 = vc2.view
        
        
        let r1start = 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)
        /*
        //避免其他手势事件的干扰
        UIApplication.sharedApplication().beginIgnoringInteractionEvents()
        //执行具体动画,不同效果的代码不同处就是在这了
        UIView.animateWithDuration(0.4, animations: {
            view1.frame = r1end
            view2.frame = r2end
        }) { (_) in
            //防止取消状态
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
            UIApplication.sharedApplication().endIgnoringInteractionEvents()
        }
     */
        
        let opts : UIViewAnimationOptions = self.interacting ? .CurveLinear : []
        if !self.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)
        
        }
    }
    
    不使用百分比驱动

    这种写法虽然看起来会稍微复杂一些,但是他的可定制程度更高,因为我们可以直接对fromView 和 toView等进行直接操作。

    不在需要具体实现animateTransition方法,但是他还是要存在的,因为他的协议必须实现的方法。

    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
        
    }
    

    将其中的代码放到实现了协议UIViewControllerInteractiveTransitioning的类中实现。放到方法

    func startInteractiveTransition(transitionContext: UIViewControllerContextTransitioning){
    }
    

    中就可以了。

    这次使用OC实现,都熟悉下:

    #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
    

    自定义Presented ViewController的过渡动画

    实现主要要两种方式

    • 不使用presentation controller
    • 使用presentation controller
    不使用presentation controller

    需要注意的地方是

    • 协议transitioningDelegate声明的位置
    • 区分present 和 dismiss
    • 只有当modalPresentationStyle *不是 .FullScreen *的时候,才能使用如下方式判定是present还是dismiss

    上代码

    import UIKit
    
    class ViewController2: UIViewController {
    
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
        //这个不能再 viewDidLoad 里执行,太晚了
        self.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!.transform = CGAffineTransformMakeScale(0.1, 0.1)
                fromView!.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!.transform = CGAffineTransformMakeScale(0.1, 0.1)
            toView!.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.completeTransition(!transitionContext.transitionWasCancelled())
            }
        }
    }
    }
    

    在.FullScreen 类型的presented中,会取得presenting View,并将其添加到containerView中,其他类型的则是,containerView是在presenting view之前的,不能给containerView添加动画,并且containerView不会移除。

    使用Presentation Controller

    在此之前,需要理解的一些东西

    • Animation Controller 用来负责动画效果,也就是presented View 如何移动到最终目标位置
    • Presentation Controller 确定presented View的最终位置,还有向containerView上添加一些视图,例如半透明效果什么的。
      举个例子来说明,如果只是实现了UIPresentationController 而没有做上面的那些工作,会使用默认动画弹出。

    接下来说说实现:
    在实现上面效果的基础上,需要在做几点工作

    • 在设置presented viewController的 transitioningDelegate属性的同时,还需要设置其的modalPresentationStyle为.Custom,这个是必须的。(注意代码位置)

         // 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
        }
      
    • 在实现另一个方法

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

    接下来便是在 UIPresentationController的子类中重载他的方法来达到我们所要的效果

    方法的说明:

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

    返回presented view的最终位置

    使用下面的方法来给containerView添加或移除一些额外的视图

    presentationTransitionWillBegin
    presentationTransitionDidEnd
    dismissalTransitionWillBegin
    dismissalTransitionDidEnd
    

    使用下面方法来个添加的额外视图进行布局

    containerViewWillLayoutSubviews
    containerViewDidLayoutSubviews
    

    方法

    shouldPresentInFullscreen
    

    默认返回的是true,如果返回false,会将presentation改变为 .CurrentContext

    主要就这几个,完结。

    Transition Coordinator的使用

    主要注意几个方法的使用就可以了

    • animateAlongsideTransition:completion:
      可以用来给已经在视图上的子视图添加动画效果

      - (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");
      }
      }
      
    • notifyWhenInteractionEndsUsingBlock:

    监控视图返回时手势动作是否成功。比如在UINavigationController中左侧滑动返回时,需要在上一个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");
    }
    }

    相关文章

      网友评论

      本文标题:自定义过渡动画

      本文链接:https://www.haomeiwen.com/subject/hvxmrttx.html