概述
iOS中不是任何對(duì)象都能處理事件, 只有繼承了UIResponder
的對(duì)象才能接收并處理事件,我們稱為響應(yīng)者對(duì)象验残。UIApplication,UIViewController,UIView都繼承自UIResponder,因此他們都是響應(yīng)者對(duì)象, 都能夠接收并處理事件。而這些事件可以分為以下三種苦酱,本文主要簡(jiǎn)單聊聊屏幕觸摸事件。包括事件傳遞
與事件響應(yīng)
肋殴。
//觸摸事件
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
//加速計(jì)事件
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;
//遠(yuǎn)程控制事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event;
事件傳遞
一般事件的傳遞是從父控件傳遞到子控件的尖殃,這個(gè)過(guò)程可以簡(jiǎn)單成為hit test
過(guò)程,字面上意思大概是『點(diǎn)擊測(cè)試』屯耸。
過(guò)程如下
UIApplication->Window->父view->子View->下一個(gè)View
注意:如果父控件接受不到觸摸事件拐迁,那么子控件就不可能接收到觸摸事件
UIView不能接收觸摸事件的三種情況:
userInteractionEnabled = NO;
hidden = YES;
alpha <= 0.01
這個(gè)過(guò)程主要是UIView的兩個(gè)函數(shù)來(lái)實(shí)現(xiàn)尋找合適view。
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
// 如果坐標(biāo)點(diǎn)在view的bounds內(nèi)疗绣,就遞歸調(diào)用 -pointInside:withEvent:方法线召,返回最合適view
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;
// 如果坐標(biāo)點(diǎn)在view的bounds內(nèi),就返回YES
hitTest函數(shù)大概原理如下
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
//如果隱藏多矮、透明度<=0.01或者不接受用戶交互都返回nil缓淹,表示這個(gè)view不是合適的view
if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
return nil;
}
//如果坐標(biāo)點(diǎn)在view 的bounds內(nèi),就遍歷所有子view
if ([self pointInside:point withEvent:event]) {
//倒序處理子view
for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
//坐標(biāo)轉(zhuǎn)換塔逃,把當(dāng)前控件上的坐標(biāo)系轉(zhuǎn)換成子控件上的坐標(biāo)系
CGPoint convertedPoint = [subview convertPoint:point fromView:self];
//遞歸判斷子view的hit test
UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
if (hitTestView) {
return hitTestView;
}
}
//如果都不符合讯壶,返回自己
return self;
}
return nil;
}
這里要注意一點(diǎn),之所以是倒序處理子view患雏,先處理后添加的子view鹏溯。這樣做是為了提高遞歸效率,因?yàn)橐话?code>后添加的子view在父view的層級(jí)關(guān)系里面的最上面淹仑,更容易找到更合適的view丙挽。
示例
如下圖所示肺孵,如果點(diǎn)擊view E,尋找最合適的view過(guò)程如下
Touch坐標(biāo)點(diǎn)在ViewA的bounds中颜阐,遞歸檢查ViewB和ViewC平窘;
Touch坐標(biāo)點(diǎn)不在ViewB的bounds中,檢查ViewC凳怨;
Touch坐標(biāo)點(diǎn)在ViewC的bounds中瑰艘,檢查ViewD和ViewE;
Touch坐標(biāo)點(diǎn)不在ViewD中肤舞,檢查ViewE紫新;
Touch坐標(biāo)點(diǎn)在ViewE中,所以ViewE為hit test的結(jié)果李剖;
執(zhí)行ViewE的touches方法芒率,進(jìn)行響應(yīng)事件。
事件響應(yīng)
上述說(shuō)到的事件傳遞
篙顺,大概是這樣的過(guò)程UIApplication->Window->父view->子View->下一個(gè)View
偶芍,經(jīng)過(guò)hit test過(guò)程找到最合適的view之后。就開(kāi)始事件響應(yīng)
,主要做的就是判斷這個(gè)最合適的view有沒(méi)有響應(yīng)touch的相關(guān)方法德玫。如果沒(méi)有實(shí)現(xiàn)匪蟀,就找上一個(gè)合適的view,一般是這個(gè)view的父類宰僧,看這個(gè)view有沒(méi)有實(shí)現(xiàn)材彪,如果一直沒(méi)有找到的話,這個(gè)touch事件就作廢拋棄了撒桨。
所以簡(jiǎn)單來(lái)說(shuō)查刻,事件傳遞是從父view到子view傳遞,而事件響應(yīng)是從子view到父view響應(yīng)的凤类。
應(yīng)用場(chǎng)景
所以,從你點(diǎn)擊屏幕一個(gè)View開(kāi)始普气,系統(tǒng)就會(huì)將UITouch和UIEvent對(duì)象打包, 放到當(dāng)前活動(dòng)的Application的事件隊(duì)列中谜疤,單例的UIApplication會(huì)從事件隊(duì)列中取出觸摸事件并傳遞給單例UIWindow,UIWindow使用hitTest:withEvent:方法查找touch操作的所在的視圖view现诀。
以下應(yīng)用場(chǎng)景主要就是修改這兩個(gè)回調(diào)函數(shù)實(shí)現(xiàn)的
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
// 如果坐標(biāo)點(diǎn)在view的bounds內(nèi)夷磕,就遞歸調(diào)用 -pointInside:withEvent:方法,返回最合適view
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;
// 如果坐標(biāo)點(diǎn)在view的bounds內(nèi)仔沿,就返回YES
不規(guī)則view的點(diǎn)擊處理
由于找view的前提是判斷坐標(biāo)點(diǎn)是否在該view的bounds內(nèi)坐桩,所以可以在pointInside方法中判斷該點(diǎn)是否符合不規(guī)則view的bounds就行,例如要實(shí)現(xiàn)一個(gè)圓形的view點(diǎn)擊處理封锉,就在pointInside方法判斷符合圓形函數(shù)內(nèi)部的點(diǎn)都返回YES便可绵跷。多個(gè)view同時(shí)響應(yīng)事件
在最終響應(yīng)的view中的touch事件處理方法直接調(diào)用上一級(jí)處理方法便可膘螟。
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"do something");
[super touchesBegan:touches withEvent:event];
}
- 指定特定view響應(yīng)事件
指定特定的view的意思是要指定符合在事件響應(yīng)鏈中的響應(yīng)者才行。
直接復(fù)寫這個(gè)view的touch處理方法便可碾局,前提要保證子view的touch處理方法沒(méi)有被復(fù)寫荆残。
參考
http://s1.downloadmienphi.net/file/downloadfile4/147/1389777.pdf