swift详解之二十七------------自定义UINavigationController的push和pop动画

自定义UINavigationController的push和pop动画



我们这里先创建一个简单的工程 , 在storyboard 中拖一个导航控制器 , rootViewController 改成我们的ViewController 。

为了实现自定义动画切换 , 我们需要实现两个协议 。 UIViewControllerAnimatedTransitioning,UINavigationControllerDelegate

UIViewControllerAnimatedTransitioning 这个协议目的是在需要使用自定义动画的同时,又不影响视图的其他属性,让你把焦点集中在动画实现的本身上,然后通过在这个协议的回调里编写自定义的动画代码,负责切换的具体内容,任何实现了这一协议的对象被称之为动画控制器。

其中有两个比较重要的方法:

transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval

animateTransition(transitionContext: UIViewControllerContextTransitioning)

第一个返回一个动画持续时间就行了 , 第二个是动画主体 。transitionContext 在这里是一个核心 ,通过这个对象能获取到切换时的上下文信息,比如从哪个VC切换到哪个VC等。

let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey);
        //let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey);
        let container = transitionContext.containerView()
        container!.addSubview((toViewController?.view)!);

       if oper == UINavigationControllerOperation.Push {

            toViewController?.view.layer.anchorPoint = CGPointMake(0, 0)
            toViewController?.view.center = CGPointMake(0, 0)
            toViewController?.view.transform = CGAffineTransformMakeRotation(CGFloat(-M_PI_2))

        UIView.animateWithDuration(self.transitionDuration(transitionContext), delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.7, options: UIViewAnimationOptions.AllowUserInteraction, animations: { () -> Void in

            toViewController?.view.transform = CGAffineTransformMakeRotation(0)
            }, completion: { (b) -> Void in
                toViewController?.view.transform = CGAffineTransformIdentity
                //动画完成
                //没有取消就执行完成 取消了 就不能完成
                transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
        })

        }else if oper == UINavigationControllerOperation.Pop{

            toViewController?.view.layer.anchorPoint = CGPointMake(1,0)  //0-1之间 不要犯傻。。
            toViewController?.view.center = CGPointMake(toViewController!.view.bounds.width, 0)
            toViewController?.view.transform = CGAffineTransformMakeRotation(CGFloat(-M_PI_2*3))

            UIView.animateKeyframesWithDuration(1.3, delay: 0, options: UIViewKeyframeAnimationOptions.AllowUserInteraction, animations: { () -> Void in

                UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration: 0.4, animations: { () -> Void in
                    toViewController?.view.transform = CGAffineTransformMakeRotation(CGFloat(-M_PI_2)*4)
                })

                UIView.addKeyframeWithRelativeStartTime(0.4, relativeDuration: 0.2, animations: { () -> Void in
                    toViewController?.view.transform = CGAffineTransformMakeRotation(CGFloat(M_PI_2/10))
                })
                UIView.addKeyframeWithRelativeStartTime(0.6, relativeDuration: 0.3, animations: { () -> Void in
                    toViewController?.view.transform = CGAffineTransformMakeRotation(CGFloat(-M_PI_2)*4)
                })

                }, completion: { (b) -> Void in
                    toViewController?.view.transform = CGAffineTransformIdentity
                    print(transitionContext.transitionWasCancelled())
                    //动画完成
                    //没有取消就执行完成 取消了 就不能完成
                    transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
            })

        }

这里是我写的一段代码, 当然我这里分了pop 和 push, oper 变量是从另一个方法中获得的,下面会说 。to 和 from没啥说的 ,container这是一个特殊的容器,切换时的动画将在这个容器中进行。其他的就是push和pop 我自己做得一个动画主体 。当然你可以根据你的需求更改这些部分 。这里主要讲自定义 , 不讲动画。

我们还需要实现另一个协议UINavigationControllerDelegate ,这个协议用来配置一些导航控制器的交互方式等。

在我们的类中定义 var navgationController:UINavigationController

初始化的时候

  init(nav:UINavigationController){
        self.navgationController = nav
        super.init()
        self.navgationController.delegate = self
    }
 func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        oper = operation
        return self
    }

我们上面的oper 就是从这里来的, 这个可以得到当前进行的是push 还是 pop 。return nil 就表示用系统默认的动画 ,return self 就表示使用我们自己定的 ,如果你只定义了一个push 或者 pop ,可以加个判断 。

这时候的效果应该是这样的:

gif有点卡顿 ,应该都有弹的效果的。。。后面会附上源码 ,大家自己运行看看

如果还要支持手势先加上这个方法

 func navigationController(navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        /**
        *  监控进度
        */
        if animationController.isKindOfClass(MyAnimation.self)
        {
            return self._interactivePushTransition
        }
        return nil;
    }

然后写两个手势操作 ,分别针对 push和pop

 func handleControllerPush(sender:UIPanGestureRecognizer){

        let location = sender.translationInView(sender.view)
        var progress = location.x / (sender.view?.bounds.size.width)!
       // print("\(location.x),\((sender.view?.bounds.size.width)!)")

        progress = max(-1, min(progress, 0));
        let pz = progress * -1
        //print(progress)
        if sender.state == UIGestureRecognizerState.Began{
            /**
            *  手势开始,新建一个监控对象
            */
            self._interactivePushTransition = UIPercentDrivenInteractiveTransition()
            /**
            *  告诉控制器开始执行push的动画
            */
            self.navgationController.pushViewController(toVc!, animated: true)

        }else if sender.state == UIGestureRecognizerState.Changed{
            /**
            *  更新手势的完成进度
            */
            //print(progress * -1)
            _interactivePushTransition?.updateInteractiveTransition(pz)

        }else if(sender.state == UIGestureRecognizerState.Ended || sender.state == UIGestureRecognizerState.Changed){
            /**
            *  手势结束时如果进度大于一半,那么就完成push操作,否则重新来过。
            */
            print(pz)
            if(pz > 0.14 ){
                self._interactivePushTransition?.finishInteractiveTransition()
            }else{
                self._interactivePushTransition?.cancelInteractiveTransition()
            }
            //当切换动画完毕时,设定interactionController为nil非常重要。如果下一个动画是非交互的,我们不希望得到一个奇怪的 interactionController
            self._interactivePushTransition = nil
        }
    }

pop的就不放了 , 自己下载代码看吧 。。

写一个自己的导航控制器 。

protocol MyPushNav{

    func pushView()->UIViewController;

}
class MyNavigationController: UINavigationController {

    var pushDele:MyPushNav?
    var animate:MyAnimation?
    override func viewDidLoad() {

        super.viewDidLoad()
        animate = MyAnimation(nav: self);
        let pan = UIPanGestureRecognizer(target: animate, action: "handleControllerPop:")
        self.view.addGestureRecognizer(pan)
    }

    override func viewDidAppear(animated: Bool) {
         pushView();
    }

    func pushView(){
        let pan2 = UIPanGestureRecognizer(target: animate, action: "handleControllerPush:")
        if let _ = pushDele {
            let sc = pushDele?.pushView()
            self.animate?.toVc = sc
            (pushDele as! UIViewController).view.addGestureRecognizer(pan2)
        }else{
            self.view.removeGestureRecognizer(pan2)
        }
    }

}

需要push的必须告知我们用push哪个,所以这些用了协议 。

pop的不用做任何操作 。

class ViewController: UIViewController,MyPushNav {

       override func viewDidLoad() {
        super.viewDidLoad()

        (self.navigationController as! MyNavigationController).pushDele = self

        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func pushView()->UIViewController {
        return SecondViewController()
    }

}

这个是push的 。。

附上一张有用的图理解理解

源码地址 :MyNavigationController

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-05 05:32:33

swift详解之二十七------------自定义UINavigationController的push和pop动画的相关文章

swift详解之二十二-----------UINavigationController的基本用法和页面传值几种方式

UINavigationController的基本用法和页面传值几种方式 本文介绍UINavigationController基本用法,因为涉及多页面顺便介绍页面传值 1.手写代码创建UINavigationController 手写方式创建很简单 , 首先创建一个项目 , 默认是从storyboard 加载的.这时候首先去掉默认加载方式 . 然后在AppDelegate.swift 的didFinishLaunchingWithOptions 中创建 代码如下: func applicatio

Android控件架构与自定义控件详解(二)——自定义View

在自定义View时,我们通常会去重写onDraw()方法来绘制View的显示内容.如果该View还需要使用wrap_content属性,那么还必须重写onMeasure()方法.另外,通过自定义attrs属性,还可以设置新的属性配置值. 在View中通常有一些比较重要的回调方法. onFinishInflate():从XML加载组件后回调. onSizeChanged(;:组件大小改变时. onMeasure():回调该方法来进行测量. onLayout():回调该方法来确定显示的位置. onT

swift详解之二十四---------------CoreAnimation(一)CALayer

CoreAnimation(一)CALayer CoreAnimation 算是一个大话题 ,要实现很多炫酷的动画,必须掌握它,掌握它之前 ,先来了解CALayer - 图层 CALayer 我们平常都是用UIView 来构建应用,CALayer 是图层的一个属性 ,view.layer . 它和UIview一样都是一些被层级关系树管理的矩形块 ,如果玩儿过PS ,就会对图层的概念比较清晰 .CALayer 也可以包含一些一些内容(像图片,文本或者背景色),管理子图层的位置,也有一些方法和属性用

Swift学习——Swift基础详解(二)

上节说了没有营养的变量和常量,这玩意,都差不多,自己稍微看下就好了 Integers    整型 整数就是整数了,没有小数,整数有符号(+,-,0)或者无符号(0,+) Swift提供了8,16,32,64位的有符号和无符号的整数,命名使用C的方式,比如,8位无符号的整型UInt8,32位有符号的整型就是Int32 Integer Bounds    整型范围 可以使用min 和 max获取整数类型的最大值和最小值 let minValue = UInt8.min // minValue is

[顶]ORACLE PL/SQL编程详解之二:PL/SQL块结构和组成元素(为山九仞,岂一日之功)

原文:[顶]ORACLE PL/SQL编程详解之二:PL/SQL块结构和组成元素(为山九仞,岂一日之功) [顶]ORACLE PL/SQL编程详解之二: PL/SQL块结构和组成元素(为山九仞,岂一日之功) 继上四篇:ORACLE PL/SQL编程之八:把触发器说透                ORACLE PL/SQL编程之六:把过程与函数说透(穷追猛打,把根儿都拔起!)                [推荐]ORACLE PL/SQL编程之四:把游标说透(不怕做不到,只怕想不到) [推荐]

iOS 开发之照片框架详解之二 —— PhotoKit 详解(下)

这里接着前文<iOS 开发之照片框架详解之二 —— PhotoKit 详解(上)>,主要是干货环节,列举了如何基于 PhotoKit 与 AlAssetLibrary 封装出通用的方法. 三. 常用方法的封装 虽然 PhotoKit 的功能强大很多,但基于兼容 iOS 8.0 以下版本的考虑,暂时可能仍无法抛弃 ALAssetLibrary,这时候一个比较好的方案是基于 ALAssetLibrary 和 PhotoKit 封装出一系列模拟系统 Asset 类的自定义类,然后在其中封装好兼容 A

安卓集成发布详解(二)gradle

转自:http://frank-zhu.github.io/android/2015/06/15/android-release_app_build_gradle/ 安卓集成发布详解(二) 15 Jun 2015 上一篇主要讲了安卓版本编译版本发布的过程,本篇主要写版本编译脚本的实现,包括签名文件处理及多渠道版本编译.安卓集成发布详解(一) 一.签名部分编写 gradle本身支持直接签名,只需要在releas部分添加如下代码即可 signingConfigs { debug { } releas

Tomcat--各个目录详解(二)

Tomcat整体目录: 一.bin文件(存放启动和关闭tomcat脚本) 其中.bat和.sh文件很多都是成对出现的,作用是一样的,一个是Windows的,一个是Linux. ① startup文件:主要是检查catalina.bat/sh 执行所需环境,并调用catalina.bat 批处理文件.启动tomcat. 异常:打开可能有闪退的问题.原因可能有以下两点: 1)缺少环境变量配置,startup会检查你的电脑环境变量是否有JAVA_HOME. 2)已经开启了Tomcat容器,再次开启端口

WebView使用详解(二)——WebViewClient与常用事件监听

登录|注册     关闭 启舰 当乌龟有了梦想-- 目录视图 摘要视图 订阅 异步赠书:Kotlin领衔10本好书      免费直播:AI时代,机器学习如何入门?      程序员8月书讯      每周荐书:Java Web.Python极客编程(评论送书) WebView使用详解(二)--WebViewClient与常用事件监听 2016-05-28 11:24 20083人阅读 评论(13) 收藏 举报  分类: 5.andriod开发(148)  版权声明:本文为博主原创文章,未经博主