hit-test的用法总结:如何阻止touch事件传递到子view

今天,群里有人问了这个问题:添加了touch事件之后怎么阻止touch事件传递到子view。其实看了官方的文档Event Handling Guide for iOS的童鞋,应该是没有问题的。但是自己还是总结一下。

触摸之后,主要的步骤如下:

(1), 事件分发:如何确定当前点击的点由哪个view来处理?
hit-test来确定hit-view
(2), 事件响应:确定hit-view之后,如何处理事件? 

当确定了hit-view之后,第一响应者就是当前的hit-view,然后就会根据响应者链来处理触摸事件。

有手势的先处理手势,手势识别失败后,执行touch系列回调处理。


情景应用
问题1:如果父视图userInteractionEnable是NO,这时候子视图能接收touch事件吗?
分析:不能
因为在hit-testing的时候父视图返回nil了,那么就轮不到子视图来hit-testing了。
这也是为何在imgView上面加载UIButton的时候,button无法响应的原因

问题2:如果一个视图A(A上面加载了手势处理)被视图B盖住了,A与B都是视图X的子视图,那么怎样让A的手势能响应?
分析: 因为B盖住了A,所以hit-test的结果之后,hit-view肯定是B,A的手势无法响应,
可以这么做:
  1, 设置B.userInteractionEnable = NO;
  2, B.hidden = YES;
  3, B.alpha = 0;
上面的3种情况下,A都可以响应手势了。
因为这么设置之后,在hit-testing的时候,B视图的hitTest方法返回的是nil,最终的hit-view是A,所以触摸事件就轮到了A来处理。

问题3:如果一个viewA不希望它的subView来处理touch事件,而是由自己处理,怎么办?
分析:
viewA不希望触摸事件传递到它的subView, 也就是viewA自己阻断触摸事件的传递,只要让触摸后最终的hit-view是他自己就可以了。
比如viewA的subView为YLViewSub1
有如下2种方法:
方法一:不推荐
在viewA的.m文件中重载hitTest(注意:viewA是一个自定义的UIView才能重载此方法),如下
-( UIView *)hitTest:( CGPoint )point withEvent:( UIEvent *)event
{
  UIView *hitView = [super hitTest:point withEvent:event];
  // 此时hitView是已经检测出的hit-view,self or subViews(hitted subView)
 
 
/* 注意:
   *
如果想要阻断触摸事件传递给 subView ,下面的 2 种做法是不太合理的:
   */
 
  /* 方法1:
   * 不管 3721 的直接返回 self 也是不对的,因为当没有点击在 self ( 包括它的 subView) 的时候, self 都成了 hit-view
   */

 
return  self;
 
  /* 方法2:
   * 因为 hitView 可能返回的是它的 subView subView subView...
   *
所以不能这么做 . 如果能确定 self subView 只有一级,这么做也是可以的 .
   */

 
if (hitView. superview == self && hitView == self ) {
   
return  self; // 点击在它的subView上也由它自己来处理,subView永远不是hit-view(永远不会是第一响应者,不处理触摸)
  }
 
  /* 正确的做法:也就是下面的方法二,在subView中重载hitTest
   * 1, 可以在 self subView 中重载 hitTest 方法,直接返回 nil, 那么点击在 self subView 上的时候,最后 hit-view 还是 self
   *
所以在重载此方法的时候一定要搞清楚具体的应用场景 .
   */

 
 
return  nil;
}
可以看到直接在viewA中重载还是不太好的实现,而且如果viewA是一个vc.view,那么就没办法重载hitTest方法了。

方法二:推荐
在viewA的subView( YLViewSub1 )的类中重载hitTest,
在YLViewSub1的.m文件中,
-( id )hitTest:( CGPoint )point withEvent:( UIEvent *)event
{
 
UIView *hitView = [ super  hitTest:point withEvent:event];

  if (hitView == self ) {
   
return  nil;
  }
else {
   
return hitView;
  }
}
有人可能有疑问了:上面说过直接在这里返回nil不就可以了,为什么还要分情况处理。
其实这要看具体的情况了,如果 YLViewSub1上面还有subView,直接的返回nil,那么就会忽略掉,所以如果你想全部忽略掉就直接返回nil,不然可以像上面这么处理。
另外,还有一种更简单的做法,直接让viewA的subView的userInteraction为NO,那么subView就不会受到触摸消息了。

扩展:
hit-test还有另外一个场景,比如viewA有B,C两个subView,但是他们2个有重合的部分,点击重合部分,那如何指定让B响应还是C来响应。
分析:
默认情况下,hit-test的时候,是从subViews的最顶上的subView开始执行hit-test,即假如先加B,再加载C,那么hit-test就是先C先B后,这样点击重合部分,那就是C就是hit-view,那么由C来响应。
如何变成让B先响应呢?
-( UIView *)hitTest:( CGPoint )point withEvent:( UIEvent *)event
{
 
UIView *hitView = [ super  hitTest:point withEvent:event];
  if (hitView == viewC) { // 点击在C上的时候,让hit-viewB,那么B就是第一响应者
    return viewB;
  }
else {
   
return hitView;
  }
}
这样,当点击重合部分就由B响应,点击B,C非重合部分由他们各自响应.

问题4:如果一个view自己不愿意处理touch事件,但是希望它的subViews处理,怎么办?
应用场景:这个问题有点sb,因为默认情况下就是subView来先处理,应用场景在哪?
可能是如果点击view自己,让他的父视图处理,点击view上面的subView由subView响应更合理?
分析:
设置view.userInteractionEnable = NO;之后,虽然自己不会响应touch事件,但是它的子view也不会响应了,
所以不能这么做。这时候就需要使用hitTest来处理,
-( UIView * )hitTest:( CGPoint )point withEvent:( UIEvent  *)event {
   UIView  *hitView = [ super   hitTest :point  withEvent :event]; 
   if  (hitView ==  self ) {
    return nil// self的时候不做处理,由它的父视图处理 
  }  else  {
   
return  hitView;  //  subView 的时候 , subView 去处理
  }
}

问题5:如果一个view自己不想处理,也不愿意往它的响应者链传递让别人处理,怎么办?
分析:首先,确定处理对象的时候必须是自己,然后,在自己这里处理的时候丢弃,
也就是自己重载响应函数,然后响应函数里面不做任何事,这样就不会继续向上传递了,也就是在自己这里做一个空处理来截止响应的处理.

问题6:一个全屏的UIView上加载了tap手势,在此view上加载一个UITableView,点击cell的时候没有执行tableview的didSelected:方法,而是响应了_onTap手势,如何处理?
解决方法:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
 
// tianyi memo:
 
// 点击在tableView上时,因为tableView自己不响应tap,所以会交给它的父视图self来响应,也就是响应_onTap:,但这不是我们想要的
 
// 我们需要点击tableView上面时,响应tableViewdidSelectRowAtIndexPath方法.点击其他空白地方相应_onTap:
 
// 返回NO表示,tap手势不会根据响应者链传递了,当前的touch对象会被忽略,也就是丢弃这个手势,
 
// 丢弃手势之后,相当于手势识别失败,然后就会走默认的touch系列回调方法,我猜测在这个时候UITableView执行了自己默认的选择cell的流程.
 
if ([touch.viewisDescendantOfView:_tableView]) {
   
returnNO;
  }
  returnYES;
}


如果有不正确或者考虑不完善的地方,欢迎指正交流。










你可能感兴趣的:(IOS)