1.事件傳遞的流程:
2.事件傳遞圖示
如果想讓某個view不能處理事件(或者說,事件傳遞到某個view那里就斷了)蜈漓,那么可以通過剛才提到的三種方式。比如宫盔,設置其userInteractionEnabled = NO;那么傳遞下來的事件就會由該view的父控件處理融虽。
例如,不想讓藍色的view接收事件灼芭,那么可以設置藍色的view的userInteractionEnabled = NO;那么點擊黃色的view或者藍色的view所產(chǎn)生的事件有额,最終會由橙色的view處理,橙色的view就會成為最合適的view。
所以巍佑,不管視圖能不能處理事件茴迁,只要點擊了視圖就都會產(chǎn)生事件,關鍵在于該事件最終是由誰來處理萤衰!也就是說堕义,如果藍色視圖不能處理事件,點擊藍色視圖產(chǎn)生的觸摸事件不會由被點擊的視圖(藍色視圖)處理脆栋!
注意:如果設置父控件的透明度或者hidden倦卖,會直接影響到子控件的透明度和hidden。如果父控件的透明度為0或者hidden = YES筹吐,那么子控件也是不可見的!
3.流程描述:
- 我們點擊屏幕產(chǎn)生觸摸事件糖耸,系統(tǒng)會將這個事件加入到一個由UIApplication管理的事件隊列中秘遏,UIApplication會從消息隊列里取事件分發(fā)下去丘薛,首先傳給UIWindow
- 在UIWindow中就會調(diào)用 hitTest:withEvent: 方法去返回一個最終響應的視圖
- 在hitTest:withEvent方法中就會去調(diào)用第二個方法 pointInside:withEvent: 去判斷當前點擊的point是否在UIWindow范圍內(nèi),如果是的話邦危,就會去遍歷它的子視圖來查找最終響應的子視圖
同級view的遍歷方式是使用倒序的方式來遍歷子視圖
洋侨,也就是說最后添加的子視圖會最先遍歷
,在每一個視圖中都去回去調(diào)用它的 hitTest:withEvent: 方法倦蚪,可以理解成為是一個遞歸調(diào)用- 最終會返回一個響應視圖希坚,如果返回的視圖有值,那么這個視圖就作為最終響應視圖陵且,結(jié)束整個事件傳遞裁僧,如果沒有值,那么就會將UIWindow作為響應值
4.hitTest:withEvent: 方法的流程
- 首先會判斷當前視圖的hiden屬性慕购、是否可以交互及透明度是否大于0.01聊疲,如果滿足條件則進入下一步,否則返回nil
- 然后調(diào)用
pointInside:withEvent:
方法來判斷這個點是否在當前范圍內(nèi)沪悲,如果滿足條件進入下一步获洲,否則返回nil- 返回以
倒序
的方式遍歷它的子視圖,在每一個子視圖中取調(diào)用hitTest:withEvent:
殿如,如果有一個子視圖返回了一個最終的響應視圖贡珊,就將這個視圖返回給調(diào)用方,結(jié)束流程涉馁。如果全部遍歷完成都沒有找到一個最終響應視圖门岔,因為點擊位置在當前視圖范圍內(nèi),就將當前視圖作為最終響應視圖返回
二烤送、視圖響應鏈
2.1 事件的分類
- multitouch events
- motion events
- remote control events
- Multitouch Events: 所謂的多點觸摸事件固歪,非常好理解,即用戶觸摸屏幕交互產(chǎn)生的事件類型。
- Motion Events: 所謂的移動事件牢裳。是指用戶在搖晃逢防,移動和傾斜手機的時候產(chǎn)生的事件成為移動事件。這類事件依賴于iPhone手機里面的加速計蒲讯,陀螺儀等傳感器忘朝。
- Remote Control Events:所謂的遠程控制事件。這個事件從名稱上面看判帮,不太好理解局嘁。但其實,這個事件指的是用戶在操作多媒體的時候產(chǎn)生的事件晦墙。比如悦昵,播放音樂、視頻等晌畅。
2.2 什么是Responder
- Responder的屬性和方法但指,從下面的方法可以看出UIResponder可以處理Touchevent,motionevent抗楔,remote control event
- (UIResponder )nextResponder;
- (BOOL)canBecomeFirstResponder; // default is NO
- (BOOL)becomeFirstResponder;
// Touch Event
- (void)touchesBegan:(NSSet<UITouch > )touches withEvent:(UIEvent )event;
- (void)touchesMoved:(NSSet<UITouch *> )touches withEvent:(UIEvent )event;
- (void)touchesEnded:(NSSet<UITouch *> )touches withEvent:(UIEvent )event;
- (void)touchesCancelled:(NSSet<UITouch *> )touches withEvent:(UIEvent )event;
// Motion Event
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent )event NS_AVAILABLE_IOS(3_0);
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent )event NS_AVAILABLE_IOS(3_0);
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent )event NS_AVAILABLE_IOS(3_0);
// Remote Control Event
(void)remoteControlReceivedWithEvent:(UIEvent)event NS_AVAILABLE_IOS(4_0);
注意有個很重要的方法棋凳,nextResponder
,很明顯可以看出來響應是一條鏈表結(jié)構(gòu)连躏,通過nestResponder找到下一個responder剩岳。
這里是從第一個responder開始通過nextresponder傳遞事件,直到有responder響應了事件就停止傳遞入热;如果傳到最后一個responder都沒有被響應拍棕,那么該事件就被拋棄。
那么勺良,誰是第一個resopnder呢绰播? responder是怎么響應的呢?responder響應后為什么不往下傳遞了呢郑气?稍后會一一回答
2.3 UIResponder的衍生類:
UIApplication UIViewController UIView都是繼承UIResponder幅垮,都可以傳遞和響應事件
那么就可以這么理解,我們看到的一個界面尾组,可能是由一個UIApplication和多個 UIViewController UIView組成忙芒,他們都是responder,他們一起組成了響應連讳侨。
每次發(fā)生觸摸事件呵萨,該事件就在這條響應鏈里傳遞
2.4 誰是第一個responder?
拿touchevent事件舉例跨跨,一般情況下(因為有開放可以主動設置firstresponder)潮峦,當前正在點擊的視圖對象就是first responder囱皿。
2.5 如何尋找first responder?
#事件傳遞的兩個核心方法
#第一個方法返回一個UIView忱嘹,是用來尋找哪一個視圖來響應這個事件
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event; // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
#第二個方法是用來判斷某一個點擊的位置 是否在視圖范圍內(nèi)嘱腥,如果在就返回YES
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event; // default returns YES if point is in bounds
hitTest和pointinside是系統(tǒng)遍歷尋找firstresponder的方法。最終返回的view就是當前觸摸的first responder
2.6 hitTest遍歷尋找first responder 的規(guī)則:
1)首先調(diào)用當前視圖的pointInside:withEvent:方法判斷觸摸點是否在當前視圖內(nèi)拘悦;
2)若返回NO,則hitTest:withEvent:返回nil;
3)若返回YES,則向當前視圖的所有子視圖(subviews)發(fā)送hitTest:withEvent:消息齿兔,所有子視圖的遍歷順序是從top到bottom,即從subviews數(shù)組的末尾向前遍歷,直到有子視圖返回非空對象或者全部子視圖遍歷完畢础米;那么分苇,后面的addsubview進來的子view就會優(yōu)先被選中為first responder
4)若第一次有子視圖返回非空對象,則hitTest:withEvent:方法返回此對象,處理結(jié)束屁桑;
注意:子view返回非空對象医寿,若該子view還擁有自己的subviews,那么步驟3是個遞歸遍歷蘑斧。
5)若所有子視圖都返回非靖秩,則hitTest:withEvent:方法返回自身(self)。
- ios沒有源碼乌叶,下面是模擬源碼寫的
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *result = [super hitTest:point withEvent:event];
CGPoint buttonPoint = [underButton convertPoint:point fromView:self];
if ([underButton pointInside:buttonPoint withEvent:event]) {
return underButton;
}
return result;
}
從上面規(guī)則可以知道盆偿,視圖超出父視圖的區(qū)域是不會參與到遍歷的柒爸,這是沒有意義的計算准浴。加上這個,一共有4種情況的view是不會參與到遍歷的
1)隱藏(hidden=YES)的視圖
2)禁止用戶操作(userInteractionEnabled=NO)的視圖
3)alpha<0.01的視圖
4)視圖超出父視圖的區(qū)域
也就是說這四種情況的視圖捎稚,以及他們的子視圖是不會成為responder的乐横。
-
下面通過一個比較直觀的圖形來講述上面的規(guī)則
image.png
假設用戶點擊了視圖D:
- 檢測到點擊坐標在View A范圍之內(nèi)。
- 繼續(xù)檢測點擊范圍是否在其子視圖B今野,C范圍內(nèi)葡公。發(fā)現(xiàn)點擊范圍在視圖C范圍內(nèi),則忽略掉B視圖及其子視圖分支条霜。
- 繼續(xù)檢測點擊范圍是否在其子視圖D范圍內(nèi)催什,如果是,則用戶當前視圖即為視圖D宰睡。如果不是蒲凶,繼續(xù)檢測其子視圖。
- 總結(jié):iOS系統(tǒng)會從父視圖向子視圖依次查找拆内,直到找到點擊范圍在當前視圖邊界范圍以內(nèi)旋圆。如果點擊范圍在某子視圖范圍內(nèi),并且沒有了子視圖麸恍,則該視圖即為當前點擊視圖灵巧。如果點擊范圍在某子視圖范圍之內(nèi),并且不在其子視圖范圍之內(nèi),則點擊視圖即為當前點擊視圖刻肄。
- 總結(jié):事件的傳遞和響應
從上面可以看出瓤球,事件的傳遞方向是(hittest就是事件的傳遞):
UIApplication -> UIWindow ->ViewController-> UIView -> initial view
而Responder傳遞方向是(還記得nextResponder嗎):
Initial View -> Parent View -> ViewController -> Window -> Application
如果最終傳遞到Application對象,依然沒有對事件作出響應敏弃,事件就會被舍棄掉冰垄。
- 怎么樣才算是對事件做出了響應呢
在事件的響應中,如果某個控件實現(xiàn)了touches...方法权她,則這個事件將由該控件來接受虹茶,如果調(diào)用了[supertouches….];就會將事件順著響應者鏈條往上傳遞,傳遞給上一個響應者隅要;接著就會調(diào)用上一個響應者的touches….方法
- 點擊蝴罪、搖動、滑動步清、旋轉(zhuǎn)等會被系統(tǒng)封裝成UIEvent要门,放到事件隊列里等待UIApplication去取,然后尋找響應者廓啊,找到對應的方法并執(zhí)行的過程就是響應欢搜。
通過上述的傳遞事件會找到第一響應者,這時就用第一響應者來響應谴轮。
-
如果第一響應者不響應炒瘟,不響應的傳遞流程示圖
視圖響應鏈.png
點擊當前視圖initial View - initial View的父視圖view - view controller - UIWindow - UIApplication
- 通過代碼來展示
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"%@ touch begin", self.class);
UIResponder *next = [self nextResponder];
while (next) {
NSLog(@"下一個響應者%@",next.class);
next = [next nextResponder];
}
}
-
打印結(jié)果:
image.png - 如果傳遞到UIApplication也沒有響應,則這個事件作廢.
轉(zhuǎn)自簡書:http://www.reibang.com/p/94b0539b2178
轉(zhuǎn)自博客:ios事件傳遞和響應機制