這篇文章主要講以下兩個方法阅爽,如果你對響應者鏈條還不夠熟悉挟伙,請接著往下看楼雹,如果足夠熟悉請?zhí)秸摹?/p>
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event ;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
響應者鏈條
在這里演示一個最簡單的按鈕響應
在主窗口控制器的view上添加一個按鈕,當用戶點擊按鈕時,系統(tǒng)究竟做了哪些事情尖阔?首先我們要知道UIResponder贮缅,只有繼承自UIResponder的對象才可以處理事件。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
這幾個很常用的方法都是從UIResponder繼承過來的介却。
回到響應者鏈條谴供。當用戶點擊按鈕,系統(tǒng)最先響應這個點擊事件齿坷,由AppDelegate接收桂肌,然后不斷上拋直到到達UIButton,如圖中實線所示永淌。當UIButton接收到這個事件崎场,UIResponder相應的方法開始工作,系統(tǒng)默認做法是調用控件對應的super方法遂蛀,值得一提的是:super會調用父類中的方法谭跨,而此處剛好會調到當前控件父控件所對應的方法
。此時的事件傳遞方向剛好相反李滴,從UIButton向底層拋饺蚊,直到系統(tǒng)接收并做出響應,如圖中虛線所示悬嗓。這一來一回,我們稱之為響應者鏈條
裕坊。
UIView中的方法
首先來回顧一下繼承關系:UIWindow\UIButton -> UIView -> UIResponder -> NSObject
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event ;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
這兩個不太常見的方法其實是屬于UIView的包竹。
上文中響應者鏈條提到過,系統(tǒng)接收用戶點擊事件后會將事件不斷上拋籍凝,直到到達UIButton周瞎,那么這中間又是那些東西在運作?
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
這個方法很好理解饵蒂,event事件在point點是否響應声诸。
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event ;
下面重點說說這個方法究竟做了些什么。
- 文字
1.判斷當前控件userInteractionEnabled退盯、hidden彼乌、alpha這三個屬性的值
2.調用 pointInside: withEvent: 方法
3.從后向前遍歷子控件泻肯,并調用子控件的 hitTest: withEvent: 和 pointInside: withEvent: 方法
- 代碼
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) {
return nil;
}
if ([self pointInside:point withEvent:event] == NO) {
return nil;
}
int count = (int)self.subviews.count - 1;
for (int i = count; i >= 0 ; i--) {
UIView *view = self.subviews[i];
CGPoint p = [view convertPoint:point fromView:self];
if ([view pointInside:p withEvent:event]) {
return [view hitTest:p withEvent:event];
break;
}
}
return self;
}
先判斷當前控件能否接收事件,如果不能返回空慰照,如果能再判斷當前點能否響應事件灶挟,如果不能返回空,如果能從后向前遍歷子控件毒租,判斷當前子控件當前點能否響應事件稚铣,如果不能繼續(xù)判斷下一個子控件直到子控件遍歷完成(如果所有子控件都不滿足條件,那么當前控件就是響應這個事件最合適的控件)墅垮,如果能那么返回當前子控件的hitTest方法并跳出循環(huán)惕医,進入子控件hitTest重復當前操作。
再解釋一下為什么要從后向前遍歷子控件算色。這個問題可以從用戶的角度思考抬伺,通常情況用戶點擊app某個位置,最好是展現在用戶最上層的控件做出響應剃允,而非底層(不排除某些特殊需求)沛简。所以蘋果選擇倒序遍歷,而不是順序斥废。
- 總結
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event ;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
通過上文分析椒楣,不難看出這兩個方法可以實現某些特殊需求,如局部攔截控件的響應事件牡肉,實現子控件在父控件之外仍可響應等等捧灰。