事件的產(chǎn)生和傳遞
先調(diào)用hittest找到最適合響應(yīng)的控件,然后再用touchbegan把事件往下傳
完整過程
1> 先將事件對象由上往下傳遞(由父控件傳遞給子控件)趟畏,找到最合適的控件來處理這個事件。
2> 調(diào)用最合適控件的touches….方法
3> 如果調(diào)用了[super touches….];就會將事件順著響應(yīng)者鏈條往上傳遞牡肉,傳遞給上一個響應(yīng)者
4> 接著就會調(diào)用上一個響應(yīng)者的touches….方法
事件的發(fā)生
蘋果注冊了一個 Source1 (基于 mach port 的) 用來接收系統(tǒng)事件彪笼,其回調(diào)函數(shù)為 __IOHIDEventSystemClientQueueCallback()钻注。
當(dāng)一個硬件事件(觸摸/鎖屏/搖晃等)發(fā)生后,首先由 IOKit.framework 生成一個 IOHIDEvent 事件并由 SpringBoard 接收配猫。這個過程的詳細(xì)情況可以參考這里队寇。SpringBoard 只接收按鍵(鎖屏/靜音等),觸摸章姓,加速,接近傳感器等幾種 Event识埋,隨后用 mach port 轉(zhuǎn)發(fā)給需要的App進程凡伊。隨后蘋果注冊的那個 Source1 就會觸發(fā)回調(diào),并調(diào)用 _UIApplicationHandleEventQueue() 進行應(yīng)用內(nèi)部的分發(fā)窒舟。
_UIApplicationHandleEventQueue() 會把 IOHIDEvent 處理并包裝成 UIEvent 進行處理或分發(fā)系忙,其中包括識別 UIGesture/處理屏幕旋轉(zhuǎn)/發(fā)送給 UIWindow 等。通常事件比如 UIButton 點擊惠豺、touchesBegin/Move/End/Cancel 事件都是在這個回調(diào)中完成的银还。
包裝成UIEvent后,系統(tǒng)會將該事件加入到一個由UIApplication
管理的事件隊列中
UIApplication會從事件隊列中取出最前面的事件洁墙,并將事件分發(fā)下去以便處理蛹疯,通常,先發(fā)送事件給應(yīng)用程序的主窗口(keyWindow)
主窗口會在視圖層次結(jié)構(gòu)中找到一個最合適的視圖來處理觸摸事件
热监,這也是整個事件處理過程的第一步
第一步的具體流程捺弦,控件不斷調(diào)用以下代碼:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
//判斷當(dāng)前狀態(tài)是否不能響應(yīng)事件
if(self.hidden == YES || self.alpha <= 0.01 || self.userInteractionEnabled == NO){
return nil;
}else if ([self pointInside:point withEvent:event] == NO){//判斷是否包含這個點,沒有就返回
return nil;
}else{//繼續(xù)遍歷
return [super hitTest:point withEvent:event];
}
}
hitest方法
4> 03-hitText方法和pointInside方法(02-事件傳遞代碼)
* (了解hitText)學(xué)習(xí)一個方法必須了解:什么時候調(diào)用和這個方法有什么用
1. hitText什么時候調(diào)用:當(dāng)一個事件傳遞給一個控件的時候孝扛,控件就會調(diào)用這個方法
2. hitText作用: 尋找到最合適的view列吼。
* (回顧下事件傳遞),UIApplication -> UIWindow
* UIWindow去尋找最合適的view? [UIWindow hitTest:withEvent:]里面做了什么事情苦始?
1> 判斷窗口能不能處理事件? 如果不能寞钥,意味著窗口不是最合適的view,而且也不會去尋找比自己更合適的view,直接返回nil,通知UIApplication陌选,沒有最合適的view理郑。
2> 判斷點在不在窗口
3> 遍歷自己的子控件蹄溉,尋找有沒有比自己更合適的view
4> 如果子控件不接收事件,意味著子控件沒有找到最合適的view,然后返回nil,告訴窗口沒有找到更合適的view,窗口就知道沒有比自己更合適的view,就自己處理事件香浩。
* 驗證下hitTest方法返回nil类缤,里面的子控件能處理事件嗎? 重寫根控制器view的hitTest:withEvent:方法邻吭,
* 驗證這個方法是否真能找到最合適的view餐弱?
* 如果點擊屏幕任何一個地方,都是白色的view囱晴,怎么做膏蚓。直接返回白色的view,就不會繼續(xù)去找白色view的子控件了。
* pointInside作用:判斷一個點在不在一個控件上
* point參數(shù):方法調(diào)用者坐標(biāo)系上的點畸写,PPT畫圖分析原理驮瞧。
示例
觸摸事件的傳遞是從父控件傳遞到子控件
- 點擊了綠色的view:
UIApplication -> UIWindow -> 白色 -> 綠色 - 點擊了藍(lán)色的view:
UIApplication -> UIWindow -> 白色 -> 橙色 -> 藍(lán)色 - 點擊了黃色的view:
UIApplication -> UIWindow -> 白色 -> 橙色 -> 藍(lán)色 -> 黃色
如果父控件不能接收觸摸事件,那么子控件就不可能接收到觸摸事件(掌握)
具體流程如下:
- 如何找到最合適的控件來處理事件枯芬?
- 自己是否能接收觸摸事件论笔?
- 觸摸點是否在自己身上?
- 從后往前遍歷子控件千所,重復(fù)前面的兩個步驟
- 如果沒有符合條件的子控件狂魔,那么就自己最適合處理
三種情況下,控件不接收觸摸事件
- 控件的userinterfaceEnable = NO淫痰;
- 透明度低于0.01
- 控件被隱藏了
而且最楷,某個控件隱藏,或者透明度低于0.01待错,它的子控件都會看不見籽孙,同樣無法接受觸摸事件, 同時火俄,UIImageView的userinterfaceEnable默認(rèn)為NO犯建。
觸摸事件處理的詳細(xì)過程,響應(yīng)者鏈條
用戶點擊屏幕后產(chǎn)生的一個觸摸事件,經(jīng)過一系列的傳遞過程后瓜客,會找到最合適的視圖控件來處理這個事件
找到最合適的視圖控件后胎挎,就會調(diào)用控件的touches方法來作具體的事件處理
touchesBegan…
touchesMoved…
touchedEnded…
這些touches方法的默認(rèn)做法是將事件順著響應(yīng)者鏈條向上傳遞,將事件交給上一個響應(yīng)者進行處理
響應(yīng)者鏈條示意圖
響應(yīng)者鏈條:是由多個響應(yīng)者對象連接起來的鏈條
作用:能很清楚的看見每個響應(yīng)者之間的聯(lián)系忆家,并且可以讓一個事件多個對象處理犹菇。
響應(yīng)者對象:能處理事件的對象
在頂級視圖(key window的視圖)上調(diào)用pointInside:withEvent:方法判斷觸摸點是否在當(dāng)前視圖內(nèi);
如果返回NO芽卿,那么A返回nil揭芍;
如果返回YES,那么它會向當(dāng)前視圖的所有子視圖(key window的子視圖)發(fā)送hitTest:withEvent:消息卸例,遍歷所有子視圖的順序是從subviews數(shù)組的末尾向前遍歷(從界面最上方開始向下遍歷)称杨。
如果有subview的hitTest:withEvent:返回非空對象則A返回此對象肌毅,處理結(jié)束(注意這個過程,子視圖也是根據(jù)pointInside:withEvent:的返回值來確定是返回空還是當(dāng)前子視圖對象的姑原。并且這個過程中如果子視圖的hidden=YES悬而、userInteractionEnabled=NO或者alpha小于0.1都會并忽略);
如果所有subview遍歷結(jié)束仍然沒有返回非空對象锭汛,則A返回頂級視圖笨奠;