1.響應(yīng)者對(duì)象
先介紹一下響應(yīng)者對(duì)象,在iOS中并不是所有的對(duì)象都能夠處理事件臼节,只有繼承UIResponder的對(duì)象才能夠接收和處理事件撬陵,我們稱(chēng)為響應(yīng)者對(duì)象。 UIApplication网缝、UIViewController巨税、UIView都繼承自UIResponder,因此它們都能接收并處理事件粉臊。
2.iOS中的事件
iOS中的事件可以分為三大類(lèi)型:
觸摸事件 草添、傳感器事件 、遠(yuǎn)程控制事件
本文主要討論觸摸事件
3.事件傳遞和事件響應(yīng)的區(qū)別
首先我們先來(lái)了解一下事件傳遞和事件響應(yīng)的區(qū)別扼仲,以便更好的理解后面的內(nèi)容 远寸。(千萬(wàn)不要混淆這兩者的概念)
事件的傳遞是從上到下的順序 (UIApplication-->UIWindow-->遞歸找到最合適處理的View),即從父控件到子控件的尋找過(guò)程
事件響應(yīng)是從下到上的順序 (實(shí)現(xiàn)View的touchesBegan方法屠凶,如果該View不能處理事件驰后,則向上傳遞給它的父控件),從子控件傳遞給父控件
4.iOS中事件的產(chǎn)生和傳遞
發(fā)生觸摸事件后矗愧,系統(tǒng)會(huì)將該事件加入到一個(gè)由UIApplication管理的事件隊(duì)列中灶芝,而不是放入棧中保存,因?yàn)殛?duì)列的特點(diǎn)是FIFO(先進(jìn)先出),先產(chǎn)生的事件先處理才符合常理
UIApplication會(huì)從事件隊(duì)列中取出最前面的事件夜涕,并將事件分發(fā)下去以便處理颤专,通常先發(fā)送事件給應(yīng)用程序的主窗口(keyWindow)
主窗口會(huì)在視圖層次結(jié)構(gòu)中找到一個(gè)最合適的視圖來(lái)處理觸摸事件,這也是整個(gè)事件處理過(guò)程的第一步钠乏。找到合適的視圖控件后栖秕,就會(huì)調(diào)用視圖控件的touches方法來(lái)作具體的事件處理
事件傳遞最重要的就是找到合適的View來(lái)接收事件, 應(yīng)用是如何找到最合適的View來(lái)接收處理事件的呢?
1.首先判斷主窗口(keyWindow)自己是否能接受觸摸事件
2.判斷觸摸點(diǎn)是否在自己身上
3.子控件數(shù)組中從后往前遍歷子控件晓避,重復(fù)前面的兩個(gè)步驟(所謂從后往前遍歷子控件簇捍,就是首先查找子控件數(shù)組中最后一個(gè)元素,然后執(zhí)行1俏拱、2步驟)
4.view暑塑,比如叫做fitView,那么會(huì)把這個(gè)事件交給這個(gè)fitView锅必,再遍歷這個(gè)fitView的子控件事格,直至沒(méi)有更合適的view為止。
5.如果沒(méi)有符合條件的子控件搞隐,那么就認(rèn)為自己最合適處理這個(gè)事件驹愚,也就是自己是最合適的view。
有兩個(gè)重要的方法 幫助應(yīng)用尋找到最合適的View
hitTest:withEvent:方法 (自定義View 實(shí)現(xiàn)該方法)
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)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;
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.沒(méi)有找到更合適的view劣纲,也就是沒(méi)有比自己更合適的view
return self;
}
pointInside:withEvent:方法
//判斷當(dāng)前點(diǎn)是否在View上
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
//判斷點(diǎn)在不在view區(qū)域
if (CGRectContainsPoint(self.btn.bounds, point)) {
return YES;
}
return NO;
}
事件的響應(yīng)
找到合適的View之后就會(huì)調(diào)用該View的touches方法進(jìn)行響應(yīng)處理具體的事件逢捺,找不到最合適的View,就不會(huì)調(diào)用touches方法進(jìn)行處理
touchesBegan方法
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(@"%s",__func__);
}
一般默認(rèn)做法是控件將事件順著響應(yīng)者鏈條向上傳遞癞季,將事件交給上一個(gè)響應(yīng)者進(jìn)行處理 (即調(diào)用super的touches方法)劫瞳。
那么如何判斷響應(yīng)者的上一個(gè)響應(yīng)者是誰(shuí)呢?有以下兩個(gè)規(guī)則
- 判斷當(dāng)前是否是控制器的View绷柒,如果是控制器的View志于,那么上一個(gè)響應(yīng)者就是控制器
- 如果不是控制器的View,那么上一個(gè)響應(yīng)者就是其父控件
如果控制器也不響應(yīng)touches方法废睦,就把事件交給UIWindow伺绽,如果UIWindow也不響應(yīng),交把事件給UIApplication郊楣,如果都不響應(yīng)事件那么事件就會(huì)拋棄作廢憔恳。
最后總結(jié)來(lái)說(shuō)一次完整的觸摸事件的傳遞響應(yīng)過(guò)程為:
UIApplication-->UIWindow-->遞歸找到最合適處理的控件-->控件調(diào)用touches方法-->判斷是否實(shí)現(xiàn)touches方法-->沒(méi)有實(shí)現(xiàn)默認(rèn)會(huì)將事件傳遞給上一個(gè)響應(yīng)者-->找到上一個(gè)響應(yīng)者-->找不到方法作廢
注:以下三種情況不能接收觸摸事件
- 不允許交互: 即空間的userInteractionEnabled = NO;
- 隱藏:隱藏的控件不能接收觸摸事件
- 透明度:如果一個(gè)控件的透明度設(shè)置 <0.01,會(huì)直接影響子控件的透明度净蚤。alpha:0.0~0.01為透明