美文网首页iOS开发iOS DeveloperiOS学习专题
从iOS的事件响应链看TableView为什么不响应touche

从iOS的事件响应链看TableView为什么不响应touche

作者: WhisperKarl | 来源:发表于2016-09-23 18:16 被阅读4044次

    问题还原:当我们需要收起TextField的键盘时,通常的做法一般是在touchBegan方法中放弃第一响应者或者直接endEditing。而当我们把一个TableView添加到控制器的View上时,touchBegan方法会不响应,原因就在于事件被TableView拦截了

    iOS的事件响应链

    事件响应链,顾名思义就是由一系列事件响应者构成的一个响应层次。当我们点击了手机屏幕上一点时,系统会通过一系列的方法找到应该由哪一个视图来响应我们的点击事件。系统是通过hitTest由UIWindow一层层向下遍历找到可以响应点击事件的子视图,知道某一个视图没有可以响应事件的子视图时,那么这个视图就是我们所说的第一响应者。我们可以写个例子来看这个过程。

    事件响应链的形成过程

    假设我们有下图的层次结构


    层级关系.png

    UIView有两个方法:

    - (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;   // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
    - (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;   // default returns YES if point is in bounds
    

    通过注释我们可以看出,第一个方法是递归返回hitTest的View对象,第二个方法是返回点击的点是否在某一个View坐标范围内。我们可以通过给UIView写一个分类来打印hitTest过程:

    - (BOOL)kr_pointInside:(CGPoint)point withEvent:(UIEvent *)event{
        
        BOOL canAnswer = [self kr_pointInside:point withEvent:event];
        NSLog(@"%@ can answer: %d",self.class,canAnswer);
        return canAnswer;
    }
    
    - (UIView *)kr_hitTest:(CGPoint)point withEvent:(UIEvent *)event{
        
        UIView *answerView = [self kr_hitTest:point withEvent:event];
        NSLog(@"hit view :%@",self.class);
        return answerView;
    }
    

    当我们点击ViewC时,我们可以看到打印信息:

    UIWindow can answer: 1
    UIView can answer: 1
    hit view :_UILayoutGuide
    hit view :_UILayoutGuide
    AView can answer: 1
    DView can answer: 0
    hit view :DView
    hit view :UILabel
    BView can answer: 1
    hit view :UILabel
    CView can answer: 1
    hit view :UILabel
    hit view :CView
    hit view :BView
    hit view :AView
    hit view :UIView
    hit view :UIWindow
    UIStatusBarWindow can answer: 1
    UIStatusBar can answer: 0
    UIStatusBarForegroundView can answer: 0
    UIStatusBarServiceItemView can answer: 0
    UIStatusBarDataNetworkItemView can answer: 0
    UIStatusBarBatteryItemView can answer: 0
    UIStatusBarTimeItemView can answer: 0
    

    从打印信息我们大概可以得到响应者查找顺序为:UIWindow->UIView->AView->DView->BView->CView
    所以当点击ViewC时我们可以得到一个响应者栈:

    C.png

    所以第一响应者就是ViewC,如果ViewC不能响应,那么逐级向上查找,如果UIWindow也不响应,事件抛弃。

    TableView为什么不响应touchBegan

    回到刚开始的问题,当我们点击TableView时,为什么touchBegan不响应呢?通过响应链我们不难想象到,当我们点击屏幕时,第一响应者应该是UITableView,而我们调用的touchBegan其实是ViewController的View的方法,所以无法被调用。
    解决方法也很简单,我们可以给tableView写一个基类,重写tableview的touchBegan方法,通过block或者代理传出,然后继承基类,即可实现touchBegan的响应。

    相关文章

      网友评论

      • 灯泡虫:在tableView中 重写 touchesBegan 并调用 [[self nextResponder] touchesBegan:touches withEvent:event]; 即可
        帅气的小时:@灯泡虫 好的,谢谢:smile:
        灯泡虫:@帅气的小时 这要看你在nextresponder中有没有做处理,一般处理不会的
        帅气的小时:你好,请问使用nextResponder对tableview的其他事件会有影响吗?
      • 低调的腹:重写tableview的touchBegan方法,为什么 在滑动的时候不走这个方法呢?
      • 京哥:这个鸡肋如何写啊:joy:
        WhisperKarl:@京哥 继承UITableView新建一个类命名为XXTableView,定义一个代理方法,在touch回调中调用代理方法,然后使用的时候直接创建XXTableView的实例,设置代理为当前控制器,在代理方法中处理touchesBegan事件即可
      • 霖溦:感谢分享,不过对于滚动视图的键盘回收,感觉还是`tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag`这样比较优雅,毕竟是系统提供的。
      • MoussyL:楼主,麻烦问一下,你那个打印hitTest:的view的分类是怎么实现的 ,能贴个demo吗? 不太明白贴出的那两个方法
        MoussyL:@WhisperKarl 好的,谢谢楼主,受教了~~~ :+1:
        WhisperKarl:@木子夕 是通过runtime交换方法实现的

        origin = class_getInstanceMethod([self class], @selector(pointInside:withEvent:));
        custom = class_getInstanceMethod([self class], @selector(kr_pointInside:withEvent:));
        method_exchangeImplementations(origin, custom);

        origin = class_getInstanceMethod([self class], @selector(hitTest:withEvent:));
        custom = class_getInstanceMethod([self class], @selector(kr_hitTest:withEvent:));
        method_exchangeImplementations(origin, custom);

      本文标题:从iOS的事件响应链看TableView为什么不响应touche

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