iOS interactivePopGestureRecognizer卡住&手势滑动监听

前言

手势侧滑导航在iOS中尤其常见,在项目中,经常和其打交道,但是经常发现,当侧滑到根控制器(第一级界面)时,继续快速使用侧滑手势popGesture会导致卡住。有时候,想要监听某个界面的侧滑手势是否结束来做一些其他操作,比如,在一个有定时器(NSTimer)的界面,使用侧滑到上一级页面,会导致该界面无法释放,这就需要知道当前侧滑手势是否已经结束,在结束时移除(NSTimer)等等。

侧滑到根控制器(第一级页面)卡住

滑动到首页(一般就是根控制器所属的第一级界面)会卡死,这是iOS系统本身存在的一个BUG,解决方案就是创建一个BaseNavigationController(继承UINavigationController),在BaseNavigationController中监听代理方法,判断当子视图控制器数viewControllers大于1时,允许pop手势可以滑动。在BaseNavigationController.m中,如下:

#pragma mark UINavigationControllerDelegate
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        // If viewController is rootViewController, disable the pop gesture recognizer.
        self.interactivePopGestureRecognizer.enabled = self.viewControllers.count > 1;
    }
}

监听interactivePopGestureRecognizer手势

一般我们很少会需要监听侧滑到上一级的手势的情况,这种场景可能会出现在一些特殊处理中,比如当使用侧滑手势pop到上一级时,由于一些操作未完成,当前self被持有,而导致dealloc方法没有被执行,换句话说,当前view controller没有释放造成内存泄漏,这种情况下,监听侧滑手势是否执行就有必要了。在BaseNavigationController.m中,如下:

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    [viewController.transitionCoordinator notifyWhenInteractionChangesUsingBlock:^(id context) {
        if (context.isCancelled) return;
        [self postPopGestureNotification];
    }];
}

- (void)postPopGestureNotification
{
    NSMutableSet *notificationNames = [NSMutableSet setWithObjects:
                                       @"FirstViewController",
                                       @"SecondViewController", nil];
   
    NSMutableSet *vcNames = [NSMutableSet set];
    for (UIViewController *vc in self.viewControllers) {
        [vcNames addObject:NSStringFromClass([vc class])];
    }
    
    [notificationNames minusSet:vcNames];

    [notificationNames enumerateObjectsUsingBlock:^(id  _Nonnull obj, BOOL * _Nonnull stop) {
        [[NSNotificationCenter defaultCenter] postNotificationName:obj object:nil];
    }];
}

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated中,可以使用- (void)notifyWhenInteractionChangesUsingBlock: (void (^)(id context))handler监听到侧滑手势的情况,如果context.isCancelledYES,说明用户侧滑的时候取消了滑动,即没有返回到上一级页面,反之,如果为NO,说明将pop到上一级页面,当前页面即将被释放。

iOS 10以下,你也可以使用- (void)notifyWhenInteractionEndsUsingBlock: (void (^)(id context))handler监听,不过iOS 10以上不建议使用,从目前情况来看,大多数项目部署最低版本都是iOS 10了,没有必要再使用废弃的API

因此,当context.isCancelledNO时,我们可以发送一个通知(notification)到当前正在操作侧滑手势pop的这个页面,在这个页面做监听,就可以在收到通知时,执行你想要的操作就可以了。

在使用过程中,需要注意的是,如果你在多个页面都需要监听侧滑手势,比如从首页 -> FirstViewController -> SecondViewController,需要在FirstViewControllerSecondViewController中都监听侧滑到上一级的手势,这就会有一个问题,无论你在哪一个页面使用侧滑手势返回时,正在监听手势的页面都会收到通知,例如,你在SecondViewController中侧滑返回到FirstViewController时,FirstViewController中的通知也会被执行!但是,一般情况下,我们肯定不希望侧滑的时候,影响到其他界面,只希望当前界面能够收到通知即可,那么如何避免这一问题呢?如上,我的做法是,把所有你要监听的view controller都以文件名字符串的形式放到一个集合中,在侧滑的时候,获取到所有的view controllers添加到另一个集合中,然后做两者的差集,这样,我们就把当前view controller前面的view controllers过滤掉了,然后遍历剩下的通知名,并发送通知,这样就可以只在当前页面即SecondViewController收到侧滑手势的监听了。

比如,在demo文末有提供)中,我们监听如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Wait 100s to execute `timeAction`
    _timer = [NSTimer scheduledTimerWithTimeInterval:100 target:self selector:@selector(timeAction) userInfo:nil repeats:NO];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(popGestureDidEnd) name:@"SecondViewController" object:nil];
}

- (void)popGestureDidEnd
{
    NSLog(@"⚠️⚠️⚠️ OOPS!SecondViewController -> popGestureDidEnd");
    [self removeTimer];
}

个人博客
Demo地址
2021-03-12

你可能感兴趣的:(iOS interactivePopGestureRecognizer卡住&手势滑动监听)