iOS 中的事件
- 觸摸事件
- 加速計(jì)事件
- 遠(yuǎn)程控制事件
響應(yīng)者對(duì)象(UIResponder)
只有繼承 UIResponder 的對(duì)象才能響應(yīng)事件
- UIApplication
- UIViewControl
- UIView
UIResponder內(nèi)部提供了以下方法來(lái)處理事件觸摸事件
- (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;
touchesBegan 這個(gè)方法拄查,如果是兩個(gè)手指同時(shí)觸摸外恕,這個(gè)方法只會(huì)調(diào)用一次擎宝,方法中包含兩個(gè) UITouch 的對(duì)象 炒考, 如果是一前一后的觸摸武花,就會(huì)調(diào)用兩次這個(gè)方法球涛,方法中包含一個(gè) UITouch 對(duì)象
注意:
1.想要處理 UIView 的觸摸事件,就要繼承 UIView ,然后重寫(xiě) UIView 的觸摸事件方法
2.UIViewControl 在其觸摸事件方法中就可以處理
UITouch 對(duì)象
UITouch 的屬性
觸摸產(chǎn)生時(shí)所處的窗口
@property(nonatomic,readonly,retain) UIWindow *window;
觸摸產(chǎn)生時(shí)所處的視圖
@property(nonatomic,readonly,retain) UIView *view
;
短時(shí)間內(nèi)點(diǎn)按屏幕的次數(shù)褥紫,可以根據(jù)tapCount判斷單擊、雙擊或更多的點(diǎn)擊
@property(nonatomic,readonly) NSUInteger tapCount;
記錄了觸摸事件產(chǎn)生或變化時(shí)的時(shí)間瞪慧,單位是秒@property(nonatomic,readonly) NSTimeInterval timestamp;
當(dāng)前觸摸事件所處的狀態(tài)
@property(nonatomic,readonly) UITouchPhase phase;
UITouch 的方法
(CGPoint)locationInView:(UIView *)view;
// 返回值表示觸摸在view上的位置
// 這里返回的位置是針對(duì)view的坐標(biāo)系的(以view的左上角為原點(diǎn)(0, 0))
// 調(diào)用時(shí)傳入的view參數(shù)為nil的話(huà)髓考,返回的是觸摸點(diǎn)在UIWindow的位置
(CGPoint)previousLocationInView:(UIView *)view;
// 該方法記錄了前一個(gè)觸摸點(diǎn)的位置
- 當(dāng)用戶(hù)用一根手指觸摸屏膜時(shí),就會(huì)創(chuàng)建一個(gè) UITouch 對(duì)象
- 一根手指對(duì)應(yīng)一個(gè) UITouch 對(duì)象
- 當(dāng)手指移動(dòng)時(shí)弃酌,系統(tǒng)會(huì)更新 UITouch 對(duì)象的位置等信息
- 當(dāng)手指離開(kāi)時(shí)氨菇,相應(yīng)的 UITouch 對(duì)象就會(huì)銷(xiāo)毀
IOS 的事件產(chǎn)生和傳遞
事件產(chǎn)生
- 發(fā)生觸摸事件后,系統(tǒng)會(huì)把事件加入到由 UIApplication 管理的事件隊(duì)列
- UIApplication 會(huì)從事件隊(duì)列中取出最前面的事件妓湘,并將事件分發(fā)下去處理查蓉,通常是先發(fā)給應(yīng)用程序的KeyWindow
- KeyWindow 會(huì)找其視圖層結(jié)構(gòu)找到一個(gè)最適合的View 來(lái)處理事件
事件的傳遞
UIApplication -> Window -> 尋找最合適的View
如何尋找最合適的 View?
- 首先判斷 KeyWind 是否能接受觸摸事件
- 判斷觸摸點(diǎn)是否在窗口身上
- 從后往前遍歷子控件榜贴,重復(fù)上述兩個(gè)步驟
- 遍歷到最合適的 View
- 如果沒(méi)有最合適的 View 豌研,那么就由 KeyWind 處理
??注意點(diǎn):
- 透明度 < 0.01 的控件,是不能接受觸摸事件
- 如果父控件不能接收事件唬党,那么子控件也不能
- 不允許交互:userInteractionEnabled = NO
- 不管控件能否接收觸摸事件鹃共,主要有觸摸,就會(huì)產(chǎn)生事件驶拱,只是這個(gè)事件會(huì)不會(huì)被處理
如何尋找最合適的 View 的底層揭秘
hitTest:withEvent:
pointInside
hitTest:withEvent:方法
- 調(diào)用:主要有事件傳遞給一個(gè)控件霜浴,控件就會(huì)調(diào)用該方法
- 作用:找到最合適的 View ,并返回該 View
攔截事件的處理
- 正因?yàn)閔itTest:withEvent:方法可以返回最合適的view屯烦,所以可以通過(guò)重寫(xiě)hitTest:withEvent:方法坷随,返回指定的view作為最合適的view。
- 不管點(diǎn)擊哪里驻龟,最合適的view都是hitTest:withEvent:方法中返回的那個(gè)view温眉。
- 通過(guò)重寫(xiě)hitTest:withEvent:,就可以攔截事件的傳遞過(guò)程翁狐,想讓誰(shuí)處理事件誰(shuí)就處理事件类溢。
如果想指定 View 作為最適合的 View,有以下兩種方法
- 在父控件的
hitTest:withEvent:
返回子控件 - 在子控件的
hitTest:withEvent:
返回自
??注意點(diǎn):
推薦方法一露懒,不推薦方法二的原因是有可能不能成功返回子控件闯冷,如果觸摸點(diǎn)不在子控件,而在另一個(gè)子控件懈词,那么沒(méi)辦法返回該子控件蛇耀。
事件傳遞的正真順序
- 產(chǎn)生觸摸事件
- UIApplication 事件隊(duì)列
- [UIWindow hitTest:withEvent:];
- 返回更合適的 View
- [子控件 hitTest:withEvent:];
- 返回更合適的 View
#import "WSWindow.h"
@implementation WSWindow
// 什么時(shí)候調(diào)用:只要事件一傳遞給一個(gè)控件,那么這個(gè)控件就會(huì)調(diào)用自己的這個(gè)方法
// 作用:尋找并返回最合適的view
// UIApplication -> [UIWindow hitTest:withEvent:]尋找最合適的view告訴系統(tǒng)
// point:當(dāng)前手指觸摸的點(diǎn)
// point:是方法調(diào)用者坐標(biāo)系上的點(diǎn)
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
// 1.判斷下窗口能否接收事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha = 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;
}
// 作用:判斷下傳入過(guò)來(lái)的點(diǎn)在不在方法調(diào)用者的坐標(biāo)系上
// point:是方法調(diào)用者坐標(biāo)系上的點(diǎn)
//- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
//{
// return NO;
//}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSLog(@"%s",__func__);
}
@end
pointInside:withEvent:方法
pointInside:withEvent:方法判斷點(diǎn)在不在當(dāng)前view上(方法調(diào)用者的坐標(biāo)系上)如果返回YES纺涤,代表點(diǎn)在方法調(diào)用者的坐標(biāo)系上;返回NO代表點(diǎn)不在方法調(diào)用者的坐標(biāo)系上译暂,那么方法調(diào)用者也就不能處理事件
事件響應(yīng)
響應(yīng)者鏈條:是由多個(gè)響應(yīng)者連接起來(lái)的鏈條。
事件響應(yīng)的流程
1.找到最合適的 View 的時(shí)候撩炊,就會(huì)調(diào)用自己 touchs 的方法處理事件外永。
2.touches默認(rèn)做法是把事件順著響應(yīng)者鏈條向上拋.
2.1 首先看 initail View 能不能處理事件
2.2 不能就把事件傳遞給父控件,繼續(xù)判斷
2.3 沒(méi)有一個(gè)控件能處理就拋給 Window
2.4 Window 處理不了拧咳,最后拋給 UIApplication
2.5 UIApplication 處理不了伯顶,把事件丟棄。
如果以上一個(gè)響應(yīng)者重寫(xiě)了 touches 方法骆膝,就能處理事件
如何做到一個(gè)事件多個(gè)對(duì)象處理
因?yàn)橄到y(tǒng)默認(rèn)做法是把事件上拋給父控件祭衩,所以可以通過(guò)重寫(xiě)自己的touches方法和父控件的touches方法來(lái)達(dá)到一個(gè)事件多個(gè)對(duì)象處理的目的。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
// 1.自己先處理事件...
NSLog(@"do somthing...");
// 2.再調(diào)用系統(tǒng)的默認(rèn)做法谭网,再把事件交給上一個(gè)響應(yīng)者處理
[super touchesBegan:touches withEvent:event];
}
事件傳遞和事件響應(yīng)的過(guò)程總結(jié)
- 事件產(chǎn)生汪厨,添加到 UIApplication 的事件隊(duì)列
- UIApplication 把事件隊(duì)列最前的事件傳遞給UIWindow
- UIWindow 調(diào)用
hitTest:withEvent:
返回一個(gè)合適的 View - View 繼續(xù)調(diào)用
hitTest:withEvent:
找到一個(gè)最適合的 View - 事件傳遞給最適合的 View 就開(kāi)始響應(yīng)事件
- 如果最適合 View 不能處理事件,把事件拋給父控件
- 一直都沒(méi)有控件可以處理事件愉择,就把事件拋給 UIWindow
- UIWindow 處理不了就拋給 UIApplication
- 最后處理不了就把事件拋棄劫乱。