前言( W/ f/ l, p# J# J
苹果在iOS 7以后给导航控制器增加了一个Pop的手势,只要手指在屏幕边缘滑动,当前的控制器的视图就会跟随你的手指移动,当用户松手后,系统会判断手指拖动出来的大小来决定是否要执行控制器的Pop操作。
; C5 ]: @7 x4 S; F. t
6 B* }! ]8 {5 ?( W$ D8 G# _4 v 这个操作的想法非常好,但是系统给我们规定的范围必须是屏幕左侧边缘才可以触发,这样实际使用过程中对于有些产品会产生不便,于是有些app就采取整个屏幕都响应这个手势并且pop动画还是用系统原生的,这样操作起来确实方便好多。
& l: x2 c7 H7 x2 W
6 x# r, p6 w) M5 M6 X- m
开始大家一定会有疑问,给控制器的View加个手势然后拖动控制器的View时改变它的frame不就可以了吗?没错,加手势这个想法是正确的。但是,由我们自己来改变控制器视图的位置是比较麻烦的,细心的朋友一定发现了,我们自定义pop手势上面的导航栏也是在随着你的手势拖拽而变动的,所以这样做还需要负责导航栏的动画,而且有一个重点问题,如果单独拖动view,这个view下面会是黑黑的一片,因为控制器的push和pop层级是由系统管理的。$ M9 `8 e, x2 n0 P3 h$ Z% F6 c6 N
- L( m# j# Q S7 X
所以走这条路虽然可以,但实现起来会比较艰辛。那么,如何实现这个效果呢?今天就给大家提供两套实现方案。
/ s6 Z# ]( u) M9 T) K& _ 方案一:自定义UIViewControllerInteractiveTransitioning对象,实现导航控制器代理方法。! y( d/ w m. X
这个是苹果官方推荐的做法,在WWDC 2013 218 - Custom Transitions Using View Controllers中有说明。
5 C; O9 ~: }! I/ C* M s 这套方案虽然实现比较麻烦,但是动画相对灵活,你可以实现这样的效果,
~( D3 \4 G, Q& F% H- i: {
Q2 R2 r# }* D& Y p j# E( m
也可以有这种效果。
; C5 O+ A7 o' }/ D; P
# V ?) U1 N3 Q: {0 @, Z2 J 其实这个拖动过程属于导航控制器的动画,所以我们需要重写UINavigationController的两个代理方法,navigationController:animationControllerForOperation:fromViewController:toViewController:7 L: l+ \4 g2 g5 B! J9 J8 |3 _+ x3 a
(名字很长下面就称为方法1)和# ?% G3 k* y$ D \
navigationController:interactionControllerForAnimationController:
. G( M( l [+ k) V Q$ l* Y2 P9 l1 a (方法2)。
: K0 o: a3 M/ Z- [3 ^. d5 }5 k1 N. W; |
# J! ^: l- g; b# I3 }4 O 解释一下他们的作用,方法1是苹果提供给我们用来重写控制器之间转场动画的(pop或者push)。方法2你可以这样理解,苹果让我们返回一个交互的对象,用来实时管理控制器之间转场动画的完成度,通过它我们可以让控制器的转场动画与用户交互(注意一点,如果方法1返回是nil,方法2是不会调用的,也就是说,只有我们自定义的动画才可以与控制器交互)。
4 c( W' J; l# f% g- w+ M; W$ c 下面我们来看一下实现过程。为了便于大家理解,我会尽量在Demo中的注释写的最清晰明了。" g1 W0 H5 X$ \, \% N) Z
同时,我们先用最简单的代码实现,在这篇文章的最后我会对本例中的Demo提供一个相对合理的写法。+ i$ s" H6 J6 V* P9 u
首先在方法1中,我们返回一个遵守了UIViewControllerAnimatedTransitioning协议的对象,它就是自定义的动画对象,我们给它起名PopAnimation,在这个类中实现两个方法来自定义转场动画。( J! n' a, P6 {2 l- i9 y5 v+ u
2 L7 X; E4 I2 x6 N; p; |
再来看方法2,我们需要返回一个遵守了UIViewControllerInteractiveTransitioning协议的对象(提示一下,这两个协议容易混淆,要注意区分,一个是负责动画,一个是负责交互过程),苹果已经有一个类专门处理这个功能,它叫UIPercentDrivenInteractiveTransition,当然你也可以自定义一个这样的类。我们可以这样理解它的作用:前面在方法1中返回的动画,会在执行的过程中被系统分解以用于用户交互,这个交互过程的动画完成度就由它来调控。下面我们来看一下如何使用它。(为了让控制器视图拖动,我们给控制器的视图加了一个拖动手势,在拖动方法里我们对这个对象进行操作)
4 u, K: W9 R! F: e8 k- v# C. J
" R- x* D8 ^; |0 C5 a
最后在视图控制器里重写导航栏的两个方法。9 \( O8 n. l" `) X& U
* g3 g6 t1 J+ @6 E7 Z 有两点不要忘记:0 y+ m/ l+ g# w' n& B$ `
6 ?/ J0 x H& ~6 a* i2 R5 \
设置导航控制器的代理为当前控制器。7 {# x/ |4 O' R2 y+ r, u1 i
: N# k$ M8 s- f" \ 给控制器加手势。
+ Q1 `( @& Y# r% S; D, D OK,这样我们就完成了这个过程。
2 y( D4 T6 N( D! w, N+ D+ B
# A7 k) ~8 T6 n$ i ?
方案二:Runtime+KVC' M4 \* q$ `) s0 r- h
要了解这样的做法,需要有Runtime的一些知识,会涉及到私有变量、私有方法的获取,但是这样做比较简单也比较有趣,如果你感兴趣就继续看下去吧。关于Runtime的知识,今后我会分享到博客里,朋友们敬请期待。
2 ?* e5 J! A8 h' g1 ]. ]7 } 为了方便大家阅读下面的代码,我们需要先了解系统的这个手势。
" i7 {4 Q a3 r2 I 前面我们了解到,这个手势属于UINavigationController,我们就跳到它的头文件里看看能不能找到线索。这个思路是正确的,确实有一个手势叫做interactivePopGestureRecognizer。属性为readonly,就是说我们不能给他换成自定义的手势,但是可以设置enable=NO。ok,既然找到了它,就打印一下看看它到底是一个什么手势。 C% q3 r: y( p3 ?9 K4 _
1 R& @- D1 p& a+ y% ]1 I8 t4 e 通过log,我们看到他属于UIScreenEdgePanGestureRecognizer这个类(之前我是没有用到过),它继承自UIPanGestureRecognizer,出现在IOS7以后,是专门处理在屏幕边缘触发的手势类型,并且只有一个属性叫edges,用来设置它的触发边缘(上、下、左、右、全部)。看到这里一些朋友会想,直接改它的edges为全部可不可以?经过试验了解到,改这个属性是没用的,它只能用来触发边缘,设为全部的意思是四个方向的边缘会触发,而且用来做控制器POP手势的只有左边缘。# Y/ e3 Y# v5 d3 j8 |7 s
我们继续看它的log。控制台除了打印了它的类,还打印了它的触发target:_UINavigationInteractiveTransition(这是一个私有类,看来是专门用来做导航控制器交互动画的),和action:handleNavigationTransition(这是它的一个私有方法),我们要做的就是新建一个UIPanGestureRecognizer,让它的触发和系统的这个手势相同,这就需要利用runtime获取系统手势的target和action。2 D2 n# _8 N6 E) B. D' ~. j! ~
那么如何获取这个target呢?一开始我用kvc想直接获取这个手势的target,程序崩溃了,原来它根本没有这样一个属性。所以我能想到的是,先利用runtime遍历它的所有成员变量,看看系统是怎么存储这个属性的, |
|