事件的產(chǎn)生與傳遞
-
事件是如何產(chǎn)生與傳遞的?
- 當(dāng)發(fā)生觸摸事件的后,系統(tǒng)會將該事件加入到一個由UIApplication管理的時間隊(duì)列
中,UIApplication會從事件隊(duì)列中取出最前面的事件,并將這個事件分發(fā)下去,以便處理,主
窗口即Window 會在視圖層次中找到一個合適的視圖來處理觸摸事件 - 觸摸事件的傳遞是從父控件傳遞給子控件的,如果父控件不能接受事件,則里面的所有子控件也不能接收事件
- 當(dāng)發(fā)生觸摸事件的后,系統(tǒng)會將該事件加入到一個由UIApplication管理的時間隊(duì)列
-
當(dāng)一個控件不能接受事件時,一般有幾種情況
- 不接收用戶交互
userInteractionEnabled = NO
- 當(dāng)一個控件隱藏時
Hidden = YES
- 當(dāng)一個控件為透明時
- 不接收用戶交互
事件的響應(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:方法底層是如何實(shí)現(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];
}
}