美文网首页
UINavigationController的全屏拖动返回

UINavigationController的全屏拖动返回

作者: 我才是臭吉吉 | 来源:发表于2019-01-25 14:42 被阅读2次

    我们知道,导航控制器的边缘返回功能使用的是“UIScreenEdgePanGestureRecognizer”类配合交互式动画进行实现。

    想要使用全屏滑动返回,可以通过两种方式进行实现:

    1. 通过苹果提供的交互式动画方式,提供动画对象和交互对象,使用UIPanGestureRecognizer对象控制交互动画的过程,实现全屏滑动返回。
    2. 使用自定义的UIPanGestureRecognizer对象通过KVC的方式“替换”掉原来的UIScreenEdgePanGestureRecognizer对象,使我们可以通过拖拽手势触发导航控制器原交互方法,以达到目的。

    这里只第二种情况进行学习总结。

    注意:本文为轻松学习之二——iOS利用Runtime自定义控制器POP手势动画的读后总结,感叹作者思路之妙。

    1. 查看手势响应对象的存储方式

    我们知道,手势触发是通过“target-action”方式进行。若想要进行手势替换,就需要了解手势是如何在UIGesture类内进行存储的。故我们先通过runtime的API对其一探究竟:

    unsigned int ivarCount = 0;
    // 指向Ivar对象数组的头指针
    Ivar *ivarList = class_copyIvarList([UIGestureRecognizer class], &ivarCount);
    for (int i = 0; i < ivarCount; i++) {
        Ivar ivar = ivarList[i];
        
        const char *name = ivar_getName(ivar);
        const char *type = ivar_getTypeEncoding(ivar);
        
        NSLog(@"name = %s, type = %s", name, type);
        
        // 有效数据为: NSMutableArray *_targets
    }
    free(ivarList);
    

    通过读取UIGestureRecognizer类的实例对象列表,可以查到,所有的手势响应的对象为类型为NSMutableArray的名为“_targets”的私有变量中。而对于内部真正对象的存储方式,我们需要在UIGestureRecognizer的实例中查看。现在,我们随意在UIView实例上添加一个手势,并通过以下方式查看我们定义的targetaction是如何存储的:

    (lldb) po [ges valueForKey:@"_targets"]
    <__NSArrayM 0x60000045e1b0>(
    (action=onGes:, target=<DetailViewController 0x7fd2f3e31fb0>)
    )
    

    在debug时,我们可以看到_target数组内部为targetaction组成的对象,在触发手势时,运行时系统就动态地向target发送action消息完成手势响应。且由于"_targets"是数组类型,可以在运行期间动态修改内部数据。这也就为我们实现全屏手势提供了可能。

    2. 探究导航控制器的返回手势

    现在,我们转回到导航控制器中,使用同样方式查看其手势实例:

    // 获取导航控制器的交互手势对象
    UIGestureRecognizer *originGesture = self.navigationController.interactivePopGestureRecognizer;
    
    // 取出存储的响应信息数组
    NSArray *targets = [originGesture valueForKey:@"_targets"];
    

    通过调试,可以看到,内部执行的targetaction对象如下所示:

    (lldb) po [originGesture valueForKey:@"_targets"]
    <__NSArrayM 0x604000450ec0>(
    (action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x7fd2f3c15fe0>)
    )
    

    其中,target为私有类_UINavigationInteractiveTransition的实例,action为名为“handleNavigationTransition:”的SEL选择器。

    3. 手势的替换

    在上面,我们已经得到了边缘拖动时绑定的target和执行的SEL,现在我们只要利用runtime的动态性,将原手势与自定义的UIPanGestureRecognizer对象进行替换,使新手势绑定原targetaction,并添加到原控制视图中,最后关闭原手势响应,即可完成神不知鬼不觉的“掉包”。完整代码如下:

    UIGestureRecognizer *originGesture = self.navigationController.interactivePopGestureRecognizer;
    // 原始手势的绑定视图(并非当前视图)
    UIView *originGestureView = originGesture.view;
    // 关闭原始手势
    originGesture.enabled = NO;
    
    NSArray *targets = [originGesture valueForKey:@"_targets"];
    id target = targets[0];
    
    // 真正的触发对象
    id realTarget = [target valueForKey:@"_target"];
    
    // 真正的回调为名为“handleNavigationTransition:”的SEL
    SEL realAction = NSSelectorFromString(@"handleNavigationTransition:");
    
    // 使用自定义的全屏拖拽手势,使触发target和action均为原navigation的边缘手势,即扩大了手势响应范围。
    UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc] initWithTarget:realTarget action:realAction];
    [originGestureView addGestureRecognizer:gesture];
    

    相关文章

      网友评论

          本文标题:UINavigationController的全屏拖动返回

          本文链接:https://www.haomeiwen.com/subject/hawmjqtx.html