利用Runtime自定义导航控制器返回手势
自从iOS7之后,系统的导航控制器就具备了边缘滑动返回的功能,这使得用户能够很方便的退出当前页面,大屏的用户不用再费力的去点击导航栏上的返回按钮,很是人性化。但是有些用户觉得这样还是不方便,只能从边缘滑动哪行啊,我要全屏都能滑!于是乎,很多应用,比如QQ、知乎等都实现了这一功能。想要实现这一功能,有好多种方法,而本文介绍的这种方法,是比较好玩的一种方法,因为我们用到了苹果私有的API。虽然违反了苹果的审核政策,但我们自有办法能躲过苹果的检测。下面,就来聊一下实现过程。
首先,我们需要知道系统的侧滑手势是如何实现的。
进入UINavigationController
的头文件里,会发现有一个手势interactivePopGestureRecognizer
,它是只读的。为了详细的了解这个属性,我们在控制台打印一下它,看看它到底是一个什么手势。
<
UIScreenEdgePanGestureRecognizer: 0x7f99d1e10ba0;
state = Possible;
delaysTouchesBegan = YES;
view = <UILayoutContainerView 0x7f99d1e0b7f0>;
target= <(action=handleNavigationTransition:,
target=<_UINavigationInteractiveTransition 0x7f99d1e0fc10>)>
>
可以看到,这个手势属于UIScreenEdgePanGestureRecognizer
这个类,它继承自UIPanGestureRecognizer
,是专门处理边缘手势的一个类。我们可以通过打印发现它的target:_UINavigationInteractiveTransition
(这是一个私有的类,用于处理导航栏动画的),action:handleNavigationTransition:
(这个就是系统实现导航栏动画的私有方法)。我们要做的,就是自己新建一个UIPanGestureRecognizer
手势,让它的target
和action
和系统的相同。
首先,我们需要先获取系统的侧滑手势的target
,用常规的手法肯定是获取不到的,因为这是系统私有属性。我们需要用runtime
遍历它的成员变量,看一下系统是如何存储这个属性的。
unsigned int count;
Ivar *ivar = class_copyIvarList([UIGestureRecognizer class], &count);
for (int i = 0; i < count; i++) {
Ivar var = ivar[i];
NSLog(@"type:===>%s",ivar_getTypeEncoding(var));
NSLog(@"name:===>%s",ivar_getName(var));
}
下面是打印的结果,我只取了两条有用的结果:
2015-09-24 15:10:30.879 Nav[1897:149271] type:===>@"NSMutableArray"
2015-09-24 15:10:30.879 Nav[1897:149271] name:===>_targets
我们再来打印一下这个_targets
数组,看看里面是什么:
NSMutableArray *_targets = [systemPopGes valueForKey:@"_targets"];
NSLog(@"%@",_targets);
打印结果如下:
("(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x7fcd0b5195c0>)")
可以看到,可变数组里存储的,就是系统实现导航栏动画的target
和action
,获取这个数组的key
就是_targets
。
所以,我们可以通过KVC
获取系统存储这个target-action
的数组,然后获取系统的target-action
,自己创建一个滑动手势,加入到系统实现侧滑手势所在的view
中,禁用系统的侧滑手势,我们自定义的手势就可以代替系统的手势,实现滑动了。
下面是实现代码:
#import "JXLNavigationController.h"
#import <objc/runtime.h>
@interface JXLNavigationController ()<UIGestureRecognizerDelegate>
@end
@implementation JXLNavigationController
- (void)viewDidLoad {
[super viewDidLoad];
//获取系统侧滑手势
UIGestureRecognizer *systemPopGes = self.interactivePopGestureRecognizer;
//禁用系统侧滑
systemPopGes.enabled = NO;
//得到系统target-action数组
NSMutableArray *_targets = [systemPopGes valueForKey:@"_targets"];
//取出系统实现侧滑的target
id systemPanTarget = [_targets.firstObject valueForKey:@"target"];
//获取系统实现侧滑的action
SEL systemAction = NSSelectorFromString(@"handleNavigationTransition:");
//自定义滑动手势
UIPanGestureRecognizer *myPan = [[UIPanGestureRecognizer alloc] initWithTarget:systemPanTarget action:systemAction];
myPan.delegate = self;
myPan.maximumNumberOfTouches = 1;
//向系统实现侧滑的view中加入自定义的滑动手势
[systemPopGes.view addGestureRecognizer:myPan];
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
//在根视图或者正在滑动时禁用手势
return self.viewControllers.count != 1 && ![[self valueForKey:@"_interactiveTransition"] boolValue];
}
@end
以上就是简单的实现了一个自定义导航栏滑动手势的UINavigationController,只要继承这个导航控制器,就可以全局实现全屏侧滑手势,当然系统版本一定要在iOS7.0以上才行。
在刚开始的时候我说到这个方法涉及苹果私有API,在发布时可能有被拒的风险,我们可以通过下面的方法简单的避免。
NSString *selectorStringBegin = @"handleNavigation";
NSString *selectorStringEnd = @"Transition:";
NSString *selectorString = [NSString stringWithFormat:@"%@%@",selectorStringBegin,selectorStringEnd];
SEL systemAction = NSSelectorFromString(selectorString);