事件是如何產(chǎn)生與傳遞的?
當(dāng)發(fā)生觸摸事件后,系統(tǒng)會將該事件加入到一個由UIApplication管理的事件隊列中. UIApplication會從時間隊列中取出最前面的時間,并將事件分發(fā)下去以便處理.主窗口會在視圖層次結(jié)構(gòu)中找到一個最合適的視圖來處理觸摸時間.
觸摸時間的傳遞是從父控件傳遞到子控件的,如果一個父控件不能接收事件,那么他里面的子控件也不能接收.
當(dāng)一個控件不能接收時間時一般有以下幾種情況
1.不接收用戶交互userInteractionEnabled = NO
2.當(dāng)一個控件隱藏時Hidden = YES
3.當(dāng)一個控件為透明白時
注意:UIImageView以及它的子控件默認(rèn)是不能接收觸摸事件的
事件的響應(yīng)
用戶點(diǎn)擊屏幕產(chǎn)生的一個觸摸事件,經(jīng)過一系列的傳遞過程后,會找到一個最適合的視圖來處理事件.找到最合適的視圖控件后,就會調(diào)用控件的touches方法來作具體的時間處理.touches的默認(rèn)做法是將事件順著響應(yīng)者鏈條向上傳遞,將事件交給上一個響應(yīng)者處理
什么是響應(yīng)者鏈條?
由多個響應(yīng)者對象連接起來的鏈條
什么是響應(yīng)者對象?
繼承了UIResponder的對象
如何去尋找上一個響應(yīng)者
1.如果當(dāng)前的View是控制器的View,那么控制器就是上一個響應(yīng)者
2.如果當(dāng)前的View不是控制器的View,那么他的父控件就是上一個響應(yīng)者
3.在視圖層次結(jié)構(gòu)的最頂級視圖,如果也不能處理收到的事件或消息,則其將事件或消息傳遞給window對象進(jìn)行處理
4.如果window對象也不處理,則其將事件或消息傳遞給UIApplication對象
5.如果UIApplication也不能處理該事件或消息,則將其丟棄
系統(tǒng)是如何尋找最合適的View
1.先判斷自己是否能接收觸摸事件
2.再判斷觸摸的當(dāng)前點(diǎn)在不在自己身上
3.如果在自己身上,它會從后往前遍歷子控件,遍歷出每一個控件后,重啟前兩步
4.如果沒有符合條件的子控件,那么自身就是最合適的View
在尋找最合適View的過程中,系統(tǒng)會調(diào)用2個方法
//作用:尋找最適合的View
//什么時候調(diào)用:當(dāng)事件傳遞給當(dāng)前View時就會調(diào)用這個方法
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
UIView *fitView = [super hitTest:point withEvent:event];
NSLog(@"%@",fitView);
return fitView;
}
//作用:判斷觸摸點(diǎn)在不在當(dāng)前的View上.
//什么時候調(diào)用:在hitTest方法當(dāng)中會自動調(diào)用這個方法.
//注意:point必須得要跟當(dāng)前View同一個坐標(biāo)系.
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
return YES;
}
那么hitTest: withEvent:方法底層是如何實現(xiàn)的呢?
// 判斷自己能否接收事件
if(self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01){
return nil;
}
// 觸摸點(diǎn)在不在自己身上
if ([self pointInside:point withEvent:event] == NO) {
return nil;
}
// 從后往前遍歷自己的子控件(重復(fù)前面的兩個步驟)
int count = (int)self.subviews.count;
for (int i = count -1; i >= 0; i--) {
UIView *childV = self.subviews[i];
// point必須得要跟childV相同的坐標(biāo)系.
// 把point轉(zhuǎn)換childV坐標(biāo)系上面的點(diǎn)
CGPoint childP = [self convertPoint:point toView:childV];
UIView *fitView = [childV hitTest:childP withEvent:event];
if (fitView) {
return fitView;
}
}
// 如果沒有符合條件的子控件,那么就自己最適合處理
return self;
在開發(fā)中或多或少會需要一些特殊的點(diǎn)擊,這里有2個小例子供大家參考
一個按鈕被一個半透明的View部分遮擋,需要點(diǎn)擊到按鈕的時候,按鈕始終響應(yīng)
一個View超出了父視圖的范圍,需要點(diǎn)擊超出范圍的View也有響應(yīng)
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
//當(dāng)觸摸點(diǎn)在按鈕上的時候,才讓按鈕去響應(yīng)事件.
//把當(dāng)前點(diǎn)轉(zhuǎn)換成按鈕坐標(biāo)系上的點(diǎn).
CGPoint btnP = [self convertPoint:point toView:self.btn];
if ( [self.btn pointInside:btnP withEvent:event]) {
return self.btn;
}else{
return [super hitTest:point withEvent:event];
}
}
其他
如何找到最合適的控件來處理事件?
1)自己是否能接收觸摸事件?
2)觸摸點(diǎn)是否在自己身上?
3)從后往前遍歷子控件數(shù)組,重復(fù)前面的兩個步驟
4)如果沒有符合條件的子控件,那么就自己最適合處理
hitTest方法內(nèi)部原理(模擬蘋果)
// 什么時候調(diào)用:只要事件一傳遞給一個控件就會調(diào)用
// 作用:尋找最合適的view給你
// UIApplication -> [UIWindow hitTest:withEvent:]尋找最合適的view告訴系統(tǒng)
// point:當(dāng)前手指觸摸的點(diǎn)
// point:是方法調(diào)用者坐標(biāo)系上的點(diǎn)
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// UIView *fitView = [super hitTest:point withEvent:event];
// 1.判斷下窗口能否接收事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
// 2.判斷下點(diǎn)在不在窗口上
// 不在窗口上
if ([self pointInside:point withEvent:event] == NO) return nil;
// 3.從后往前遍歷子控件數(shù)組
int count = (int)self.subviews.count;
// 0 1
for (int i = count - 1; i >= 0; i--) {
// 獲取子控件
UIView *childView = self.subviews[i];
// 坐標(biāo)系的轉(zhuǎn)換,把窗口上的點(diǎn)轉(zhuǎn)換為子控件上的點(diǎn)
// 把自己控件上的點(diǎn)轉(zhuǎn)換成哪個控件上點(diǎn)
CGPoint childP = [self convertPoint:point toView:childView];
UIView *fitView = [childView hitTest:childP withEvent:event];
if (fitView) {// 如果能找到最合適的view
return fitView;
}
}
// 4.沒有比自己更合適的view
return self;
}
注意這個方法,可以主動攔截事件的傳遞
// 作用:判斷下傳入過來的點(diǎn)在不在方法調(diào)用者的坐標(biāo)系上
// point:是方法調(diào)用者坐標(biāo)系上的點(diǎn)
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
return NO;
}
如果重寫該方法直接返回no
那么就能成功在這一層攔截事件,也就是說事件傳遞到這一層就告訴你觸摸事件處理不了
UIView不接收觸摸事件的三種情況
1、不接收用戶交互
userInteractionEnabled = NO
2、隱藏
hidden = YES
3、透明
alpha = 0.0 ~ 0.01
提示:UIImageView的userInteractionEnabled默認(rèn)就是NO,因此UIImageView以及它的子控件默認(rèn)是不能接收觸摸事件的