原文:理解點擊屏幕的事件響應(yīng)--->對UIView的hitTest: withEvent: 方法的理解
作者:??歲月崢嶸·我輩當先
當用戶點擊屏幕后,UIApplication 先響應(yīng)事件,然后傳遞給UIWindow。如果window可以響應(yīng)。就開始遍歷window的subviews。遍歷的過程中,如果第一個遍歷的view1可以響應(yīng),那就遍歷這個view1的subviews(依次這樣不停地查找,直至查找到合適的響應(yīng)事件view)。如果view1不可以響應(yīng),那就開始對view2進行判斷和子視圖的遍歷。依次類推view3,view4…… ?如果最后沒有找到合適的響應(yīng)view,這個消息就會被拋棄。(整個遍歷的過程就是樹的先序遍歷)。

理解了上面的圖后,我們再來看看這兩個方法。
為了方便,我們將
- (nullable UIView*)hitTest:(CGPoint)point withEvent:(nullable UIEvent?*)event;稱為方法A
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent?*)event;稱為方法B
對view進行重寫這兩個方法后,點擊屏幕后,首先響應(yīng)的是方法A;
如果方法A中,我們沒有調(diào)用父類([super hitTest:point withEvent:event];)的這個方法,那就根據(jù)這個方法A的返回view,作為響應(yīng)事件的view。(當然返回nil,就是這個view不響應(yīng))
如果方法A中,我們調(diào)用了父類的方法([super hitTest:point withEvent:event];)那這個時候系統(tǒng)就要調(diào)用方法B;通過這個方法的返回值,來判斷當前這個view能不能響應(yīng)消息。
如果方法B返回的是no,那就不用再去遍歷它的子視圖。方法A返回的view就是可以響應(yīng)事件的view。
如果方法B返回的是YES,那就去遍歷它的子視圖。(就是上圖我們描述的那樣,找到合適的view返回,如果找不到,那就由方法A返回的view去響應(yīng)這個事件。)
總結(jié)下來:
返回一個view來響應(yīng)事件 (如果不想影響系統(tǒng)的事件傳遞鏈,在這個方法內(nèi),最好調(diào)用父類的這個方法)
- (nullable UIView*)hitTest:(CGPoint)point withEvent:(nullable UIEvent?*)event{
? ? return [super hitTest:point withEvent:event]; }
返回的值可以用來判斷是否繼續(xù)遍歷子視圖(返回的根據(jù)是觸摸的point是否在view的frame范圍內(nèi))
- (BOOL)pointInside:(CGPoint)point withEvent:(nullableUIEvent?*)event;? ? ??
出現(xiàn)視圖或者控件無法響應(yīng)的情況的可能原因
1.self.hidden = YES; (控件被隱藏了)
2.self.userInteractionEnabled = NO (視圖的交互性關(guān)閉了)
3.self.alpha <= 0.01 (視圖的透明度為0.01以下)
4.屏幕點擊點 不包含在你需要響應(yīng)的視圖里面(可能是父視圖沒有frame,或者點擊位置超過父視圖范圍)
? ? ? ? ·? 以上4種問題都可以實現(xiàn)下面這個方法來直接檢測出來是否響應(yīng)
- (UIView*)hitTest:(CGPoint)pointwithEvent:(UIEvent*)event{
?? if (self.hidden || !self.userInteractionEnabled || self.alpha <= 0.01) { ?
? ? ? return nil;//無響應(yīng)
? ? } ?? ? ?
? if([self pointInside:point withEvent:event]) { ??
? ? ? ? ? ? ? for(UIView* subView in[self.subviews reverseObjectEnumerator]) { ??
? ? ? ? ? ? ? ? ? ? ? CGPoint convertedPoint = [subView? convertPoint:point fromView:self]; ??
? ? ? ? ? ? ? ? ? ? ? UIView* hitTestView = [subView hitTest:convertedPoint withEvent:event];
?? ? ? ? ? ? ? ? ? ? ? ? if(hitTestView) { ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? return hitTestView;//找到響應(yīng)的視圖 可以響應(yīng) ?? ?
? ? ? ? ? ? ? ? ? ? ? ? } ?? ? ? ? ?
? ? ? ? ? } ?? ? ? ? ?
? } ?? ?
? ? return nil;//不響應(yīng)
? ?//return [super hitTest:point withEvent:event];?
}
屏幕穿透點擊
兩個重疊的視圖,響應(yīng)下面的視圖
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event
{? ? for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
? ? ? ? CGPoint convertedPoint = [subview convertPoint:point fromView:self];
? ? ? ? UIView*hitTestView = [subview hitTest:convertedPoint withEvent:event];
//判斷視圖是否是你想要穿透的視圖類 如果是就不響應(yīng)該視圖
? ? ? ? if (hitTestView.tag == 2) { ? ? ? ? ? ? return nil; ? ? ? ? }
? ? ? ? if ([hitTestView isKindOfClass:
[UICollectionReusableView class]]) {? ? ? ? ? ? return nil;// 如果兩個重疊的視圖不在一個父視圖?
? ? ? ? ? ? return 你要響應(yīng)的視圖;? ? ?//如果兩個重疊的視圖在同一個父視圖上
? ? ? ? }
? ? ? ? if(hitTestView) {
? ? ? ? ? ? return hitTestView;
? ? ? ? }
? ? }
? ? return[super hitTest:point withEvent:event];
}