UINavigationController的全屏pop之runtime探究

对之前一直写一直用的功能,来做个总结。
系统自带的pop效果是轻扫左边边缘pop返回,要实现的效果是轻扫全屏pop返回。

思路

要改变系统的效果,1.重写,2.设置系统提供的相关属性(直接设置/通过KVC)

探究

首先,我们先去UINavigationController.h源文件中找系统提供的方法或者属性。@property(nullable, nonatomic, readonly) UIGestureRecognizer *interactivePopGestureRecognizer NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED;,interactivePopGestureRecognizer是只读属性,属于UIGestureRecognizer这个类。因为是readonly的,所以我们无法重写和自定义,继续点进去UIGestureRecognizer.h查看,有一个enabled属性,所以可以将enabled设置为NO,或者猜测是否有私有属性可以通过KVC搞定的。
于是,log一下interactivePopGestureRecognizer一看究竟

NSLog(@"%@",self.navigationController.interactivePopGestureRecognizer);

打印结果

; target= <(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x7fd9a16003c0>)>>

通过log,interactivePopGestureRecognizer属性其实是UIScreenEdgePanGestureRecognizer类,查看UIScreenEdgePanGestureRecognizer.h源文件,继承自UIPanGestureRecognizer,包含一个属性edges,也就是所有边缘,从这个枚举看出都是设置边缘的,无法修改edges为全部

typedef NS_OPTIONS(NSUInteger, UIRectEdge) {
    UIRectEdgeNone   = 0,
    UIRectEdgeTop    = 1 << 0,
    UIRectEdgeLeft   = 1 << 1,
    UIRectEdgeBottom = 1 << 2,
    UIRectEdgeRight  = 1 << 3,
    UIRectEdgeAll    = UIRectEdgeTop | UIRectEdgeLeft | UIRectEdgeBottom | UIRectEdgeRight
} NS_ENUM_AVAILABLE_IOS(7_0);

所以,我们要做的是,创建一个UIPanGestureRecognizer手势,让它的target和action执行系统响应的方法,所以可以用runtime获取系统手势的target和action

unsigned int count = 0;
Ivar *var = class_copyIvarList([UIGestureRecognizer class], &count);
for (int i = 0; i < count; i++) {
    Ivar _var = *(var + i);
    NSLog(@"%s ------ %s",ivar_getName(_var),ivar_getTypeEncoding(_var));
}
打印结果

接下来,可以通过KVC获取_targets了

NSMutableArray *targets = [self.navigationController.interactivePopGestureRecognizer valueForKeyPath:@"_targets"];
NSLog(@"%@",targets);
/*
(
    "(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x7fdbf2e02110>)"
)
*/

_targets数组中就一个元素,虽然不知道什么类型,可以选择用 id 接收。如果想继续探究,那就打断点看下控制台,isa指向UIGestureRecognizerTarget私有类。


断点调试
代码

我们可以在自定义的NavigationController中添加以下代码

@interface SSNavigationController ()

@end

@implementation SSNavigationController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self initGlobalPan];
}

-(void)initGlobalPan
{
    //取消系统自带手势
    self.interactivePopGestureRecognizer.enabled = NO;
    
    //获取系统的手势的target数组
    NSMutableArray *_targets = [self.interactivePopGestureRecognizer valueForKeyPath:@"_targets"];
    //获取target
    id target = [[_targets firstObject] valueForKeyPath:@"_target"];
    //获取action
    SEL action = NSSelectorFromString(@"handleNavigationTransition:");
    
    //创建一个与系统一样的手势 只把它的类改为UIPanGestureRecognizer
    UIPanGestureRecognizer *popRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget: target action: action];
    popRecognizer.delegate = self;
    //添加到系统手势作用的view上
    UIView *gestureView = self.interactivePopGestureRecognizer.view;
    [gestureView addGestureRecognizer:popRecognizer];
}

-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    //当前控制器为根控制器,pop动画正在执行的时候不允许手势
    return self.viewControllers.count != 1 && ![[self valueForKeyPath:@"_isTransitioning"] boolValue];
}

@end

你可能感兴趣的:(UINavigationController的全屏pop之runtime探究)