根據右邊的圖來簡要說一下hitTest檢測流程:
view接收到hitTest消息會通過自己調用pointInSize:withEvent:來判斷該點是不是在自己內部。下面以(Judge)來表示view通過該方法的判斷結果。
觸點1--A(Judge = YES)--B(Judge = NO)、D(Judge = NO)、E(Judge = NO)、F(Judge = NO)--A
觸點2--A(Judge = YES)--B(Judge = YES)--C(Judge = YES)--C
觸點3--A(Judge = YES)--B(Judge = NO) ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? --E相對D在頂部,優先遍歷E(Judge = YES)--E
觸點4--不在A內--nil
歸納:
hitTest:會首先在 application 的 keyWindow 上調用(UIWindow 也是 UIView 的子類),并且該方法的返回值將被用來處理事件。會先判斷產生觸摸的 point 是否發生在自己的 bounds 內,如果沒有將返回 nil;如果 point 在自己的范圍內,則會為自己的每個子視圖調用 hitTest: 方法,只要有一個子視圖通過這個方法返回一個 UIView 對象,那么整個方法就一層一層地往上返回;如果沒有子視圖返回 UIView 對象,則父視圖將會把自己返回。
補充:
如果某個view的enabled或userInteractionEnable為NO或者alpha<0.01,那么該view的hitTest永遠(pointInSize:withEvent:返回YES/NO)都會返回nil,這意味著它和它的子視圖沒有機會去接收和處理事件。
在自定義控件時,我們經常要響應一個并非是矩形的區域,那么這時候,就可以通過重寫View的pointInSize:withEvent:來限制響應區域了。
默認的寫法:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
return CGRectContainsPoint(self.bounds, point);
}
現在想將范圍限定在一個圓形內:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
BOOL inside = [self.superview pointInside:point withEvent:event];
if (inside) {
CGFloat radius = self.frame.size.width / 2;
CGFloat dx = point.x - radius;
CGFloat dy = point.y - radius;
CGFloat distace = sqrt(dx * dx + dy * dy);
return distace < radius;
}
return inside;
}
附:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (self.alpha <= 0.01 || !self.userInteractionEnabled || self.hidden) {
return nil;
}
BOOL inside = [self pointInside:point withEvent:event];
UIView *hitView = nil;
if (inside) {
NSEnumerator *enumerator = [self.subviews reverseObjectEnumerator];
for (UIView *subview in enumerator) {
if (hitView) {
break;
}
}
if (!hitView) {
hitView = self;
}
return hitView;
} else {
return nil;
}
}