iOS事件攔截和事件轉(zhuǎn)發(fā)

響應鏈流程

基本流程

大家都知道 iOS 的響應鏈是 UIApplication 收到用戶觸摸屏幕的事件以后通過逐層尋找最后得到用戶觸摸的 View 也就是第一響應者,然后調(diào)用 View 的 touchesBegan:withEvent: 方法處理事件任務的流程.大概流程是這樣的:

原圖地址 http://www.cocoachina.com/ios/20160113/14896.html

圖片很清晰的說明了查找流程 AppDelegate 收到事件逐層查找.最終找到 UIButton 這個響應者 然后調(diào)用 UIButton 的touchesBegan:withEvent: 方法處理事件.

如何查找第一響應者

查找第一響應者主要涉及以下兩個方法

- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;

pointInside:
通過 point 參數(shù)確定觸碰點是否在當前 View 的響應范圍內(nèi) 是則返回YES 否則返回 NO 實現(xiàn)方法大概是這個樣子的

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
    
    return CGRectContainsPoint(self.bounds, point);
}

hitTest方法:

  1. 它首先會通過調(diào)用自身的 pointInside 方法判斷用戶觸摸的點是否在當前對象的響應范圍內(nèi),如果 pointInside 方法返回 NO hitTest方法直接返回 nil
  2. 如果 pointInside 方法返回 YES hitTest方法接著會判斷自身是否有子視圖.如果有則調(diào)用頂層子視圖的 hitTest 方法 直到有子視圖返回 View
  3. 如果所有子視圖都返回 nil hitTest 方法返回自身.

hitTest方法的內(nèi)部實現(xiàn)偽代碼

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    // 判斷觸摸位置是否在當前視圖內(nèi)
    if ([self pointInside:point withEvent:event]) {
        NSArray<UIView *> * superViews = self.subviews;
        // 倒序 從最上面的一個視圖開始查找
        for (NSUInteger i = superViews.count; i > 0; i--) {
            UIView * subview = superViews[i - 1];
            // 轉(zhuǎn)換坐標系 使坐標基于子視圖
            CGPoint newPoint = [self convertPoint:point toView:subview];
            // 得到子視圖 hitTest 方法返回的值
            UIView * view = [subview hitTest:newPoint withEvent:event];
            // 如果子視圖返回一個view 就直接返回 不在繼續(xù)遍歷
            if (view) {
                return view;
            }
        }
        // 所有子視圖都沒有返回 則返回自身
        return self;
    }
    return nil;
}

事件傳遞

找到第一響應者 application 便會根據(jù) event 調(diào)用第一響應者響應的
touch 方法:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;
- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches NS_AVAILABLE_IOS(9_1);

第一響應者在這幾個方法中處理響應的事件,處理完成后根據(jù)需要調(diào)用 nextResponder 的 touch 方法,通常 nextResponder 就是第一響應者的 superView 文章的第一張圖倒著看就是nextResponder 的順序

事件攔截

通常第一響應者都是響應鏈中最末端的響應者,事件攔截就是在響應鏈中截獲事件,停止下發(fā).將事件交由中間的某個響應者執(zhí)行.比如這樣:


通常點擊紅色 view 事件將交由 紅色 view 處理.如果想讓粉色 View 或者綠色 view 處理事件應該怎么辦?
有兩種辦法

  1. 在紅色 view 的的 touch 方法中調(diào)用父類或者 nextResponder 的
    touch 方法
  2. 在需要攔截的 view 中重寫 hitTest 方法改變第一響應者

首先來看第一種

 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // 將事件傳遞給下一響應者
    [self.nextResponder touchesBegan:touches withEvent:event];
    // 調(diào)用父類的touch方法 和上面的方法效果一樣 這兩句只需要其中一句
    [super touchesBegan:touches withEvent:event];
    
}

這種方法有兩個問題,你需要重寫所有的 touch 方法并且還要重寫要攔截事件的 view 與頂級 view 之間的所有 view 的 touch 方法

第二種方法
重寫攔截事件的 view 的 hitTest 方法 比如要讓綠色的 view 處理事件 就重寫綠色 view 的 hitTest 方法

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    // 如果在當前 view 中 直接返回 self 這樣自身就成為了第一響應者 subViews 不再能夠接受到響應事件
    if ([self pointInside:point withEvent:event]) {
        return self;
    }
    return nil;
}

這種方法比較簡單粗暴.實現(xiàn)后 所有 subview 將不再能夠接受任何事件 具體使用那種方式看需求.當然還可以通過 event 或者 point 有針對性的攔截

事件轉(zhuǎn)發(fā)

有時候還需要將事件轉(zhuǎn)發(fā)出去.讓本來不能響應事件的 view 響應事件,最常用的場景就是讓子視圖超出父視圖的部分也能響應事件,比如要實現(xiàn)這樣的 tabbar

9A58AA4E-0685-4F08-A3AE-06E9C627EA08.png

橙色按鈕有兩個區(qū)域 a 區(qū)超出父視圖 b 區(qū)沒有超出父視圖,如果不作處理,那么點擊 a 區(qū)是無法響應事件的,因為 a 區(qū)域的坐標不在父視圖的范圍內(nèi),當執(zhí)行到父視圖的 pointInside 的時候就會返回 NO
想要讓 a 區(qū)響應事件 就需要重寫父視圖的 pointInside 或 hitTest 方法讓 pointInside 返回 YES 或 讓hitTest 直接返回橙色視圖
重寫hitTest

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
    
    // 觸摸點在視圖范圍內(nèi) 則交由父類處理
    if ([self pointInside:point withEvent:event]) {
        return [super hitTest:point withEvent:event];
    }
    
    // 如果觸摸點不在范圍內(nèi) 而在子視圖范圍內(nèi)依舊返回子視圖
    NSArray<UIView *> * superViews = self.subviews;
    // 倒序 從最上面的一個視圖開始查找
    for (NSUInteger i = superViews.count; i > 0; i--) {

        UIView * subview = superViews[i - 1];
        // 轉(zhuǎn)換坐標系 使坐標基于子視圖
        CGPoint newPoint = [self convertPoint:point toView:subview];
        // 得到子視圖 hitTest 方法返回的值
        UIView * view = [subview hitTest:newPoint withEvent:event];
        // 如果子視圖返回一個view 就直接返回 不在繼續(xù)遍歷
        if (view) {

            return view;
        }
    }
    
    return nil;
}

重寫 pointInside 方法原理相同 重點注意轉(zhuǎn)換坐標系 就算他們不是一條響應鏈上 也可以通過重寫 hitTest 方法轉(zhuǎn)發(fā)事件.原理相同的東西就不再寫了

擴展

關于手勢的處理邏輯和這個相同.但是手勢的優(yōu)先級更高.如果父視圖有手勢.默認優(yōu)先處理手勢事件 可以修改手勢的屬性cancelsTouchesInView為 NO 來同時處理手勢和普通觸摸事件

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內(nèi)容