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

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