1. 程式人生 > >hit-test的用法總結:如何阻止touch事件傳遞到子view

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可能返回的是它的subViewsubViewsubView...
   *
所以不能這麼做. 如果能確定selfsubView只有一級,這麼做也是可以的.
   */
if (hitView.superview == self && hitView == self) {
   
return self; // 點選在它的subView上也由它自己來處理,subView永遠不是hit-view(永遠不會是第一響應者,不處理觸控)
  }
  /* 正確的做法:也就是下面的方法二,在subView中過載hitTest    * 1,可以在selfsubView中過載hitTest方法,直接返回nil,那麼點選在selfsubView上的時候,最後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 = [superhitTest: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; } 如果有不正確或者考慮不完善的地方,歡迎指正交流。