苹果在IOS7之后给导航控制器增长了一个Pop的手势,只要手指在屏幕边缘滑动,当前的控制器的视图就会跟随你的手指移动,当用户松手后,系统会判断手指拖动出来的大小来决定是否要执行控制器的Pop操做。git
nav_pop_origin.gifgithub
这个操做的想法很是好,可是系统给咱们规定的范围必须是屏幕左侧边缘才能够触发,这样实际使用过程当中对于有些产品会产生不便,因而有些app就采起整个屏幕都响应这个手势而且pop动画仍是用系统原生的,这样操做起来确实方便好多。数组
nav_pop_custom.gifapp
开始你们必定会有疑问,给控制器的View加个手势而后拖动控制器的View时改变它的frame不就能够了吗?没错,加手势这个想法是正确的。可是,由咱们本身来改变控制器视图的位置是比较麻烦的,细心的朋友必定发现了,咱们自定义pop手势上面的导航栏也是在随着你的手势拖拽而变更的,因此这样作还须要负责导航栏的动画,并且有一个重点问题,若是单独拖动view,这个view下面会是黑黑的一片,由于控制器的push和pop层级是由系统管理的。ide
nav_pop_failed.gif学习
因此走这条路虽然能够,但实现起来会比较艰辛。那么,如何实现这个效果呢?今天就给你们提供两套实现方案。优化
[1]动画
这个是苹果官方推荐的作法,在WWDC 2013 218 - Custom Transitions Using View Controllers中有说明。spa
这套方案虽然实现比较麻烦,可是动画相对灵活,你能够实现这样的效果,设计
nav_pop_cube.gif
也能够有这种效果。
nav_pop_flip.gif
其实这个拖动过程属于导航控制器的动画,因此咱们须要重写UINavigationController的两个代理方法,navigationController:animationControllerForOperation:fromViewController:toViewController:(名字很长下面就称为方法1)和
navigationController:interactionControllerForAnimationController:(方法2)。
解释一下他们的做用,方法1是苹果提供给咱们用来重写控制器之间转场动画的(pop或者push)。方法2你能够这样理解,苹果让咱们返回一个交互的对象,用来实时管理控制器之间转场动画的完成度,经过它咱们可让控制器的转场动画与用户交互(注意一点,若是方法1返回是nil,方法2是不会调用的,也就是说,只有咱们自定义的动画才能够与控制器交互)。
下面咱们来看一下实现过程。为了便于你们理解,我会尽可能在Demo中的注释写的最清晰明了。
同时,咱们先用最简单的代码实现,在这篇文章的最后我会对本例中的Demo提供一个相对合理的写法。
首先在方法1中,咱们返回一个遵照了UIViewControllerAnimatedTransitioning协议的对象,它就是自定义的动画对象,咱们给它起名PopAnimation,在这个类中实现两个方法来自定义转场动画。
屏幕快照 2015-03-28 下午6.49.05.png
再来看方法2,咱们须要返回一个遵照了UIViewControllerInteractiveTransitioning协议的对象(提示一下,这两个协议容易混淆,要注意区分,一个是负责动画,一个是负责交互过程),苹果已经有一个类专门处理这个功能,它叫UIPercentDrivenInteractiveTransition,固然你也能够自定义一个这样的类。咱们能够这样理解它的做用:前面在方法1中返回的动画,会在执行的过程当中被系统分解以用于用户交互,这个交互过程的动画完成度就由它来调控。下面咱们来看一下如何使用它。(为了让控制器视图拖动,咱们给控制器的视图加了一个拖动手势,在拖动方法里咱们对这个对象进行操做)
屏幕快照 2015-03-29 下午12.33.59.png
最后在视图控制器里重写导航栏的两个方法。
屏幕快照 2015-03-29 下午12.37.51.png
有两点不要忘记:
OK,这样咱们就完成了这个过程。
nav_pop_own.gif
要了解这样的作法,须要有Runtime的一些知识,会涉及到私有变量、私有方法的获取,可是这样作比较简单也比较有趣,若是你感兴趣就继续看下去吧。关于Runtime的知识,从此我会分享到博客里,朋友们敬请期待。
为了方便你们阅读下面的代码,咱们须要先了解系统的这个手势。
前面咱们了解到,这个手势属于UINavigationController,咱们就跳到它的头文件里看看能不能找到线索。这个思路是正确的,确实有一个手势叫作interactivePopGestureRecognizer。属性为readonly,就是说咱们不能给他换成自定义的手势,可是能够设置enable=NO。ok,既然找到了它,就打印一下看看它究竟是一个什么手势。
屏幕快照 2015-03-26 下午5.17.35.png
经过log,咱们看到他属于UIScreenEdgePanGestureRecognizer这个类(以前我是没有用到过),它继承自UIPanGestureRecognizer,出如今IOS7之后,是专门处理在屏幕边缘触发的手势类型,而且只有一个属性叫edges,用来设置它的触发边缘(上、下、左、右、所有)。看到这里一些朋友会想,直接改它的edges为所有可不能够?通过试验了解到,改这个属性是没用的,它只能用来触发边缘,设为所有的意思是四个方向的边缘会触发,并且用来作控制器POP手势的只有左边缘。
咱们继续看它的log。控制台除了打印了它的类,还打印了它的触发target:_UINavigationInteractiveTransition(这是一个私有类,看来是专门用来作导航控制器交互动画的),和action:handleNavigationTransition(这是它的一个私有方法),咱们要作的就是新建一个UIPanGestureRecognizer,让它的触发和系统的这个手势相同,这就须要利用runtime获取系统手势的target和action。
那么如何获取这个target呢?一开始我用kvc想直接获取这个手势的target,程序崩溃了,原来它根本没有这样一个属性。因此我能想到的是,先利用runtime遍历它的全部成员变量,看看系统是怎么存储这个属性的,
屏幕快照 2015-03-29 下午3.25.02.png
经过log咱们能够看到,UIGestureRecognizer有一个叫_targets的属性,它的类型为NSMutableArray。
屏幕快照 2015-03-29 下午3.25.09.png
它是用数组来存储每个target-action,因此能够动态的增长手势触发对象。那么又是什么存储每个target-action呢?为了了解这个咱们拿到这个属性的名字"_targets"经过kvc获取它,接着打印出来。
屏幕快照 2015-03-29 下午3.33.54.png
屏幕快照 2015-03-29 下午3.34.01.png
能够看到,因为系统重写了它的description方法,因此咱们没办法经过打印获取这个对象是什么类型。既然不能打印,那么咱们就用断点调试,来看它的真实类型,
屏幕快照 2015-03-29 下午3.37.32.png
咱们看到,原来每个target-action是用UIGestureRecognizerTarget这样一个类来存储的,它也是一个私有类。
苹果把许多的类作私有化也是有缘由所在,其实在平时咱们拿到这个类也是没有用的,他们的目的之一是避免对开发者公开无用的类,影响了封装性。因此在类的设计上,仍是要向苹果学习。
下面直接看代码。
咱们在控制器的ViewDidLoad加上这段代码,而且它只须要执行一次。
屏幕快照 2015-03-29 下午4.07.48.png
这个demo我会提供给你们,下面简单说下程序的优化思路。
优化点一:对于方案一,其实不该该把导航控制器的代理方法以及手势处理的方法交给视图控制器,由于这段代码不是属于某一个视图控制器,而是全局的导航控制器,因此咱们应该参考苹果的设计思想:新建一个专门管理交互过程的对象,这个类咱们叫作NavigationInteractiveTransition。
优化点二:再来看以前的ViewDidLoad中只执行一次的代码,其实写在这里也不够稳当,一样的,这段代码也不属于某一个Controller,优化方案是新建一个导航控制器,在这个导航控制器的viewDidLoad中写上这些代码,这样也并不须要dispatch once。
优化点三:因为咱们自定义的手势是加在一个私有view上,这个view是一个全局的,因此当这个控制器为根控制器时,咱们的手势仍是在起做用,这就至关于对根控制器作了pop操做,这会出现一个错误nested pop animation can result in corrupted navigation bar。致使这个错误的缘由还有一个,若是咱们pop的动画正在执行,再去触发一次手势,会致使导航控制器和导航条的动画混乱。为了不问题出现咱们须要成为手势的代理,判断当前控制器是否为根控制器而且pop或者push动画是否在执行(这个变量是私有的,须要用kvc来获取)。
屏幕快照 2015-03-30 下午5.06.24.png
通过最后的优化,视图控制器能够什么都不写,想使用这个效果,只要使用咱们自定义的导航控制器就能够了,这样的好处是手势动画与控制器彻底解耦,而且不用给每个控制器都addGesture。
给你们推荐一个仓库https://github.com/nst/iOS-Runtime-Headers,这个仓库能够调取苹果的全部私有方法头文件,至关强大。
最后放上这个demo的地址:https://github.com/zys456465111/CustomPopAnimation(使用时,切换工程的scheme就能切换不一样方案。对于方案二,只须要导航控制器的类就能够了。)
感谢你们,轻松学习系列还会继续下去,我会尽可能写出更多通俗易懂的文章,让开发变得轻松起来,个人微博:http://weibo.com/JazysYu 。
文/J_雨(简书做者) 原文连接:http://www.jianshu.com/p/d39f7d22db6c 著做权归做者全部,转载请联系做者得到受权,并标注“简书做者”。