想要學(xué)習(xí)事件的產(chǎn)生與響應(yīng)過(guò)程首先要了解什么是響應(yīng)者對(duì)象,什么是響應(yīng)者鏈條。
響應(yīng)者對(duì)象:繼承了UIResponder的對(duì)象稱之為響應(yīng)者對(duì)象,也就是能處理事件的對(duì)象。
響應(yīng)者鏈條:是由多個(gè)響應(yīng)者對(duì)象連接起來(lái)的鏈條。
其次,并不是所有的控件都可以接收事件,在以下五種情況下控件不能接收事件:
-
不接收用戶交互時(shí)不能夠處理事件
userInteractionEnabled = NO;
注意:UIImageView的userInteractionEnabled默認(rèn)就是NO
-
當(dāng)一個(gè)控件隱藏的時(shí)候不能夠接收事件
Hidden = YES
-
當(dāng)一個(gè)控件為透明的時(shí)候也不能夠接收事件
alpha <= 0.01;
父控件不能接收事件,那么子控件也不能接收事件
子控件超出父控件的大小
事件是怎樣產(chǎn)生與傳遞的?
當(dāng)用戶點(diǎn)擊屏幕后會(huì)產(chǎn)生一個(gè)觸摸事件,系統(tǒng)會(huì)將該事件加入到一個(gè)由UIApplication管理的事件隊(duì)列中。UIApplication會(huì)從事件隊(duì)列中取出最前面的事件,并將事件傳遞給先發(fā)送事件給應(yīng)用程序的主窗口以便處理,主窗口會(huì)在視圖層次結(jié)構(gòu)中找到一個(gè)最合適的視圖來(lái)處理觸摸事件。
怎么尋找處理事件最合適的視圖 view ?
- 先判斷自己是否能夠接收觸摸事件,如果能再繼續(xù)往下判斷;
- 再判斷觸摸的當(dāng)前點(diǎn)在不在自己的身上;
- 如果在自己身上,它會(huì)從后往前遍歷子控件,遍歷出每一個(gè)子控件后,重復(fù)前面的兩個(gè)步驟;
- 如果沒有符合條件的子控件,那么它本身就是要尋找的最適合的View。
在尋找最合適的視圖 view 中,需要使用到 hitTest
與 PointInside
兩個(gè)方法。
hitTest 方法與 PointInside 方法
-
hitTest 方法
-
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
- 作用:尋找最適合的View
- 參數(shù):當(dāng)前手指所在的點(diǎn),產(chǎn)生的事件
- 返回值:返回誰(shuí),誰(shuí)就是最適合的View
- 只要傳遞給一個(gè)控件時(shí),就會(huì)調(diào)用這個(gè)控件的 hitTest 方法
-
-
PointInside 方法
-
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{ return YES; }
- 作用:判斷point在不在方法調(diào)用者上
- point:必須是方法調(diào)用者的坐標(biāo)系
- hitTest方法底層會(huì)調(diào)用這個(gè)方法,判斷點(diǎn)在不在控件上
-
-
hitTest 方法的底層實(shí)現(xiàn)
① 判斷當(dāng)前控件能不能接收事件
if(self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) { return nil; }
② 判斷觸摸點(diǎn)在不在當(dāng)前的控件上
if(![self pointInside:point withEvent:event]) { return nil; }
③ 從后往前遍歷自己的子控件
int count = (int)self.subviews.count; for (int i = count - 1; i >= 0;i-- ) { UIView *childV = self.subviews[i]; //把當(dāng)前坐標(biāo)系上的點(diǎn)轉(zhuǎn)換成子控件坐標(biāo)系上的點(diǎn) CGPoint childP = [self convertPoint:point toView:childV]; //判斷自己的子控件是不是最適合的View UIView *fitView = [childV hitTest:childP withEvent:event]; //如果子控件是最適合的View,直接返回 if (fitView) { return fitView; } //否則自己就是最適合的View return self; }
事件產(chǎn)生、傳遞與響應(yīng)的完整過(guò)程
- 當(dāng)用戶點(diǎn)擊屏幕后會(huì)產(chǎn)生一個(gè)觸摸事件,系統(tǒng)會(huì)將該事件加入到一個(gè)由UIApplication管理的事件隊(duì)列中;
- UIApplication會(huì)從事件隊(duì)列中取出最前面的事件,并將事件傳遞給先發(fā)送事件給應(yīng)用程序的主窗口以便處理;
- 主窗口會(huì)調(diào)用 hitTest 方法尋找最適合的視圖控件,找到后就會(huì)調(diào)用視圖控件的 touches 方法來(lái)做具體的事情;
- 當(dāng)調(diào)用 touches 方法,它的默認(rèn)做法會(huì)將事件順著響應(yīng)者鏈條往上傳遞;
-
傳遞給上一個(gè)響應(yīng)者,接著就會(huì)調(diào)用上一個(gè)響應(yīng)者的touches方法。
響應(yīng)者鏈條示意圖
如何尋找上一個(gè)響應(yīng)者?
- 如果當(dāng)前的View是控制器的View,那么控制器就是上一個(gè)響應(yīng)者
- 如果當(dāng)前的View不是控制器的View,那么它的父控件就是上一個(gè)響應(yīng)者
- 在視圖層次結(jié)構(gòu)的最頂級(jí)視圖,如果也不能處理收到的事件或消息,則其將事件或消息傳遞給window對(duì)象進(jìn)行處理
- 如果window對(duì)象也不處理,則其將事件或消息傳遞給UIApplication對(duì)象
- 如果UIApplication也不能處理該事件或消息,則將其丟棄