在iOS中只有繼承UIResponder的對象才能夠接收并處理事件,UIResponder 是所有響應對象的基類,在UIResponder類中定義了處理上述各種事件的接口。我們熟悉的 UIApplication、 UIViewController、 UIWindow 和所有繼承自UIView的UIKit類都直接或間接的繼承自UIResponder,所以它們的實例都是可以構成響應者鏈的響應者對象,首先我們通過一張圖來簡單了解一下事件的傳遞以及響應.

1.響應者鏈條
響應者鏈條就是由多個響應者對象連接起來的鏈條,它的作用就是讓我們能夠清楚的看見每個響應者之間的聯(lián)系,并且可以讓一個時間多個對象處理.
2.響應過程
iOS系統(tǒng)檢測到手指觸摸(Touch)操作時會將其打包成一個UIEvent對象,并放入當前活動Application的事件隊列,單例的UIApplication會從事件隊列中取出觸摸事件并傳遞給單例的UIWindow來處理,UIWindow對象首先會使用hitTest:withEvent:方法尋找此次Touch操作初始點所在的視圖(View),即需要將觸摸事件傳遞給其處理的視圖(最合適來處理的控件),這個過程稱之為hit-test view。
那么什么是最適合來處理事件的控件?
1.自己能響應觸摸事件
2.觸摸點在自己身上
3.從后往前遞歸遍歷子控件, 重復上兩步
4.如果沒有符合條件的子控件, 那么就自己最合適處理
3.兩個重要的響應方法(UIView的)
1.hit-test view:事件傳遞給控件的時候, 就會調用該方法,去尋找最合適的view并返回看可以響應的view
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// 1.如果控件不允許與用用戶交互,那么返回nil
if (self.userInteractionEnabled == NO || self.alpha <= 0.01 || self.hidden == YES){
return nil;
}
// 2. 如果點擊的點在不在當前控件中,返回nil
if (![self pointInside:point withEvent:event]){
return nil;
}
// 3.從后往前遍歷每一個子控件
for(int i = (int)self.subviews.count - 1 ; i >= 0 ;i--){
// 3.1獲取一個子控件
UIView *childView = self.subviews[i];
// 3.2當前觸摸點的坐標轉換為相對于子控件觸摸點的坐標
CGPoint childP = [self convertPoint:point toView:childView];
// 3.3判斷是否在在子控件中找到了更合適的子控件(遞歸循環(huán))
UIView *fitView = [childView hitTest:childP withEvent:event];
// 3.4如果找到了就返回
if (fitView) {
return fitView;
}
}
// 4.沒找到,表示沒有比自己更合適的view,返回自己
return self;
}
- pointInside: 該方法判斷觸摸點是否在控件身上,是則返回YES,否則返回NO,point參數(shù)必須是方法調用者的坐標系.
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
return NO;
}
4.涉及的相關面試題
1.事件的傳遞方向: 事件傳遞是從上自下傳遞,響應是從下到上,所謂的上就是父視圖而已,也就是離窗口最近的.
2.穿透控件:
2.1 如果我們不想讓某個視圖響應事件,只需要重載 PointInside:withEvent:方法,讓此方法返回NO就行了.
2.2 若是view上有view1,view1上有view2,點擊view2,view2自己響應,點擊view1,view1不響應,只有view響應,也就是隔層傳遞
/*
重載view1的此方法,如果點在自己身上,且子控件中有最合適的響應者,就返回對應子控件,否則就不響應,并將該事件隨著響應者鏈條往回傳遞,交給上一個響應者來處理. (即調用super的touches方法)
誰是上一個響應者?
1. 如果view的控制器存在,就傳遞給控制器;如果控制器不存在,則將其傳遞給它的父視圖
2. 在視圖層次結構的最頂級視圖,如果也不能處理收到的事件或消息,則其將事件傳遞給window對象進行處理
3. 如果window對象也不處理,則其將事件或消息傳遞給UIApplication對象
4. 如果UIApplication也不能處理該事件或消息,則將其丟棄
*/
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
CGRect frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
BOOL value = (CGRectContainsPoint(frame, point));
NSArray *views = [self subviews];
for (UIView *subview in views) {
value = (CGRectContainsPoint(subview.frame, point));
if (value) {
return value;
}
}
return NO;
}
例如放大控件響應區(qū)域,view上有n個子視圖,點擊其中一個讓另一個來響應等等,都是可以通過重載pointInside來達到目的.
如有問題,歡迎指正分享~
.End