iOS事件傳遞&視圖響應鏈

1.事件傳遞的流程:
事件傳遞圖.png
2.事件傳遞圖示
事件傳遞詳解.png

如果想讓某個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幅垮,都可以傳遞和響應事件


image.png

那么就可以這么理解,我們看到的一個界面尾组,可能是由一個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)。

事件傳遞圖解.png
  • 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:

  1. 檢測到點擊坐標在View A范圍之內(nèi)。
  2. 繼續(xù)檢測點擊范圍是否在其子視圖B今野,C范圍內(nèi)葡公。發(fā)現(xiàn)點擊范圍在視圖C范圍內(nèi),則忽略掉B視圖及其子視圖分支条霜。
  3. 繼續(xù)檢測點擊范圍是否在其子視圖D范圍內(nèi)催什,如果是,則用戶當前視圖即為視圖D宰睡。如果不是蒲凶,繼續(xù)檢測其子視圖。
  4. 總結(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事件傳遞和響應機制

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末第步,一起剝皮案震驚了整個濱河市疮装,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌粘都,老刑警劉巖廓推,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異翩隧,居然都是意外死亡樊展,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門堆生,熙熙樓的掌柜王于貴愁眉苦臉地迎上來专缠,“玉大人,你說我怎么就攤上這事顽频√僦” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵糯景,是天一觀的道長嘁圈。 經(jīng)常有香客問我省骂,道長,這世上最難降的妖魔是什么最住? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任钞澳,我火速辦了婚禮,結(jié)果婚禮上涨缚,老公的妹妹穿的比我還像新娘轧粟。我一直安慰自己,他們只是感情好脓魏,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布兰吟。 她就那樣靜靜地躺著,像睡著了一般茂翔。 火紅的嫁衣襯著肌膚如雪混蔼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天珊燎,我揣著相機與錄音惭嚣,去河邊找鬼。 笑死悔政,一個胖子當著我的面吹牛晚吞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播谋国,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼槽地,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了烹卒?” 一聲冷哼從身側(cè)響起闷盔,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤弯洗,失蹤者是張志新(化名)和其女友劉穎旅急,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體牡整,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡藐吮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了逃贝。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谣辞。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖沐扳,靈堂內(nèi)的尸體忽然破棺而出泥从,到底是詐尸還是另有隱情,我是刑警寧澤沪摄,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布躯嫉,位于F島的核電站纱烘,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏祈餐。R本人自食惡果不足惜擂啥,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望帆阳。 院中可真熱鬧哺壶,春花似錦、人聲如沸蜒谤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鳍徽。三九已至塌碌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間旬盯,已是汗流浹背台妆。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留胖翰,地道東北人接剩。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像萨咳,于是被迫代替她去往敵國和親懊缺。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355