iOS-使用hitTest控制點擊事件的響應(yīng)對象

之前在文章《iOS-實現(xiàn)映客首頁TabBar和滑動隱藏NavBar和TabBar》中驶沼,提到了hitTest方法夯秃,但是沒有詳細(xì)說明,導(dǎo)致有童鞋不理解為什么要這么做苇经,這幾天把hitTest的資料整理了一下,在這里介紹一些宦言,解開疑惑扇单。

這篇文章,最終的目的就是解釋如何讓中間按鈕超出TabBar部分響應(yīng)點擊事件奠旺。效果圖如下:

中間按鈕超出了`TabBar`的區(qū)域效果圖

這篇文章將圍繞一下幾個問題來講:

  1. hitTest是什么
  2. hitTest的調(diào)用順序是怎么樣的
  3. hitTest和事件傳遞有什么關(guān)系
  4. hitTest是如何解決子視圖超出其視圖范圍還是能響應(yīng)觸摸事件的

下面我們一個一個來看蜘澜。

1. hitTest是什么

hitTest:withEvent:UIView的一個方法,該方法會被系統(tǒng)調(diào)用响疚,是用于在視圖(UIView)層次結(jié)構(gòu)中找到一個最合適的UIView來響應(yīng)觸摸事件鄙信。

2. hitTest的調(diào)用順序是怎么樣的

一個觸摸事件事件傳遞順序大致如下:

touch->UIApplication->UIWindow->UIViewController.view->subViews->...->view

1) 觸摸事件傳遞順序

  1. 當(dāng)用戶點擊屏幕時怖糊,會產(chǎn)生一個觸摸事件脆烟,系統(tǒng)會將該事件加入到由UIApplication管理的事件隊列中
  2. UIApplication會從事件隊列中取出最早的事件進行分發(fā)處理,先發(fā)送事件給應(yīng)用程序的主窗口UIWindow
  3. 主窗口會調(diào)用其hitTest:withEvent:方法在視圖(UIView)層次結(jié)構(gòu)中找到一個最合適的UIView來處理觸摸事件

2) hitTest調(diào)用順序

以下pointInside:withEvent:簡稱為pointInside圈膏,hitTest:withEvent:簡稱為hitTest

hitTest的代碼邏輯大致如下:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    //系統(tǒng)默認(rèn)會忽略isUserInteractionEnabled設(shè)置為NO践盼、隱藏鸦采、alpha小于等于0.01的視圖
    if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
        return nil;
    }
    if ([self pointInside:point withEvent:event]) {
        for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
            CGPoint convertedPoint = [subview convertPoint:point fromView:self];
            UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
            if (hitTestView) {
                return hitTestView;
            }
        }
        return self;
    }
    return nil;
}

執(zhí)行順序如下:

  1. 首先在當(dāng)前視圖的hitTest方法中調(diào)用pointInside方法判斷觸摸點是否在當(dāng)前視圖內(nèi)
  2. pointInside方法返回NO,說明觸摸點不在當(dāng)前視圖內(nèi)咕幻,則當(dāng)前視圖的hitTest返回nil渔伯,該視圖不處理該事件
  3. pointInside方法返回YES,說明觸摸點在當(dāng)前視圖內(nèi)肄程,則從最上層的子視圖開始(即從subviews數(shù)組的末尾向前遍歷)锣吼,遍歷當(dāng)前視圖的所有子視圖,調(diào)用子視圖的hitTest方法重復(fù)步驟1-3
  4. 直到有子視圖的hitTest方法返回非空對象或者全部子視圖遍歷完畢
  5. 若第一次有子視圖的hitTest方法返回非空對象蓝厌,則當(dāng)前視圖的hitTest方法就返回此對象玄叠,處理結(jié)束
  6. 若所有子視圖的hitTest方法都返回nil,則當(dāng)前視圖的hitTest方法返回當(dāng)前視圖本身拓提,最終由該對象處理觸摸事件

上面的流程诸典,看著可能有點繞,我們來看下面一個例子


例子

上圖中有5個View崎苗,紅點為手指點擊區(qū)域狐粱,ViewA為父視圖,ViewBViewCViewA的子視圖胆数,ViewDViewEViewC的子視圖肌蜻。
(這里假設(shè)所有View都可以響應(yīng)點擊事件,而且ViewBViewC上層必尼,ViewDViewE上層蒋搜,即ViewBaddSubView:執(zhí)行在ViewC之后,ViewDaddSubView:執(zhí)行在ViewE之后)

當(dāng)點擊ViewE時判莉,hitTest執(zhí)行順序如下:
先看看點擊大致走向圖如下豆挽,其中,?部分為執(zhí)行pointInsideYES部分券盅,X部分執(zhí)行pointInsideNO部分帮哈,最終hitTest返回ViewE

hitTest走向圖

  1. 首先調(diào)用ViewAhitTest方法,由于觸摸點在其范圍內(nèi)锰镀,pointInside返回YES娘侍,遍歷其子視圖,依次調(diào)用ViewBViewChitTest方法
  2. 執(zhí)行ViewBhitTest方法泳炉,由于觸摸點是不在ViewB內(nèi)憾筏,其pointInside方法返回NOhitTest返回nil
  3. 執(zhí)行ViewChitTest方法花鹅,由于觸摸點是在ViewC內(nèi)氧腰,其pointInside方法返回YES,遍歷其子視圖刨肃,依次調(diào)用ViewDViewEhitTest方法
  4. 執(zhí)行ViewDhitTest方法古拴,由于觸摸點是不在ViewD內(nèi),其pointInside方法返回NO之景,所以其hitTest返回nil
  5. 執(zhí)行ViewEhitTest方法斤富,由于觸摸點是在 ViewE內(nèi),其pointInside方法返回YES锻狗,由于其沒有子視圖了满力,其hitTest返回其本身
  6. 最終,由ViewE來響應(yīng)該點擊事件

3. hitTest和事件傳遞有什么關(guān)系

事件傳遞的的順序和hitTestpointInside返回為YES的視圖的執(zhí)行順序是相反的轻纪。事件傳遞是從最上層的視圖開始傳遞的油额,直到UIApplication

拿我們上面的例子來說刻帚,hitTest執(zhí)行的結(jié)果是ViewE來響應(yīng)事件潦嘶,但是如果ViewE并不處理該事件,則其需要把該事件進行傳遞給下一個響應(yīng)者崇众,這個時候掂僵,它會將事件拋給ViewC航厚,如果ViewC也不處理事件,則其會將事件傳遞給ViewA锰蓬,如果ViewA也不處理幔睬,則該事件就不響應(yīng)了。

以下由蘋果官方文檔提供的事件傳遞圖


蘋果官方提供的事件傳遞圖

上圖事件的傳遞流程如下:

  1. 首先芹扭,由initial view嘗試來處理事件麻顶,如果它處理不了,則會將事件傳遞給他的父視圖View
  2. View嘗試處理該事件舱卡,如果其也處理不了辅肾,再傳遞給它的父視圖UIViewController.view
  3. UIViewController.view嘗試來處理該事件,如果處理不了轮锥,將把該事件傳遞給UIViewController
  4. UIViewController嘗試處理該事件矫钓,如果處理不了,將把該事件傳遞給主窗口Window
  5. 主窗口Window嘗試來處理該事件交胚,如果處理不了份汗,將傳遞給應(yīng)用單例Application
  6. 如果應(yīng)用單例Application也處理不了,則該事件將會被丟棄

4. hitTest是如何解決子視圖超出其視圖范圍還是能響應(yīng)觸摸事件的

我們來看看下面的圖蝴簇,下圖中中間按鈕超出了TabBar的區(qū)域

中間按鈕超出了`TabBar`的區(qū)域效果圖

我們通過Xcode中下圖紅框按鈕來查看該頁面的層級關(guān)系
Xcode中層級查看按鈕

我來看下這個圖的層級關(guān)系

層級結(jié)構(gòu)圖

從以上圖可以看出杯活,TabBarUITableView,共同的父類為UILayoutContainerView熬词,而TabBar的層級旁钧,相對于UITableView高些,它和UITransitionView是同級的互拾。

當(dāng)我們點擊中間按鈕超出TabBar部分(“中間按鈕超出了TabBar
的區(qū)域效果圖”紅框部分)歪今,系統(tǒng)是如何處理的呢?我們跳過UIWindow颜矿,直接從UILayoutContainerView開始調(diào)用hitTest寄猩。

先看看大致走向圖,其中骑疆,?部分為執(zhí)行pointInsideYES部分田篇,X部分執(zhí)行pointInsideNO部分

hitTest走向圖
  1. 調(diào)用UILayoutContainerViewhitTest方法,由于是在其區(qū)域內(nèi)箍铭,pointInside返回YES泊柬,再遍歷其子視圖,調(diào)用hitTest
  2. 先調(diào)用TabBarhitTest方法诈火,由于點擊區(qū)域是在TabBar之外的兽赁,所以pointInside返回NOhitTest返回nilTabBar并不響應(yīng)該事件
  3. 再調(diào)用UITransitionViewhitTest方法刀崖,在其區(qū)域內(nèi)惊科,遞歸調(diào)用子視圖hitTest方法,直到調(diào)用UITableView在突出按鈕后的UITableViewCellhitTest返回蒲跨,返回該Cell译断,最終由Cell響應(yīng)該事件

所以,系統(tǒng)默認(rèn)的處理方式或悲,超出TabBar區(qū)域,中間按鈕是不響應(yīng)該事件的堪唐,而是由其后視圖響應(yīng)巡语。

想要超出父視圖區(qū)域響應(yīng)點擊事件,必須將走向圖該為如下所示(其中淮菠,?部分為執(zhí)行pointInsideYES部分男公,X部分執(zhí)行pointInsideNO部分):

修改后的hitTest走向圖

要讓中間按鈕響應(yīng)點擊超出TabBar按鈕部分的點擊事件,則需要重寫TabBarhitTest方法了合陵,在執(zhí)行hitTest方法時枢赔,判斷點擊區(qū)域在中間按鈕的區(qū)域,則返回中間按鈕拥知,響應(yīng)該事件踏拜,代碼如下:

//重寫hitTest方法,去監(jiān)聽中間按鈕的點擊低剔,目的是為了讓凸出的部分點擊也有反應(yīng)
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    
    //判斷當(dāng)前手指是否點擊到中間按鈕上速梗,如果是,則響應(yīng)按鈕點擊襟齿,其他則系統(tǒng)處理
    //首先判斷當(dāng)前View是否被隱藏了姻锁,隱藏了就不需要處理了
    if (self.isHidden == NO) {
        
        //將當(dāng)前tabbar的觸摸點轉(zhuǎn)換坐標(biāo)系,轉(zhuǎn)換到中間按鈕的身上猜欺,生成一個新的點
        CGPoint newP = [self convertPoint:point toView:self.centerBtn];
        
        //判斷如果這個新的點是在中間按鈕身上位隶,那么處理點擊事件最合適的view就是中間按鈕
        if ( [self.centerBtn pointInside:newP withEvent:event]) {
            return self.centerBtn;
        }
    }
    
    return [super hitTest:point withEvent:event];
}

童鞋的疑問

這里,之前童鞋有一個疑問:
問:直接在中間按鈕中事件hitTest直接來響應(yīng)點擊事件开皿,行不行呢涧黄?
答:答案當(dāng)然是不行的,如果你看懂了這篇文章副瀑,那就知道答案了弓熏。如果不在TabBar中重寫hitTest方法,系統(tǒng)是先調(diào)用TabBarhitTest方法的糠睡,在調(diào)用該hitTest方法時挽鞠,判斷點擊超出TabBar部分,不在其區(qū)域內(nèi),pointInside就返回NO了信认,hitTest直接返回nil材义,TabBar不能響應(yīng)該事件,其子視圖(中間按鈕)也就沒機會執(zhí)行hitTest方法了嫁赏。所以是不行的其掂。

參考文章

如果覺得該文章對你有幫助,請幫忙點贊潦蝇,如果發(fā)現(xiàn)有錯誤款熬,請幫忙指出,謝謝攘乒!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末贤牛,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子则酝,更是在濱河造成了極大的恐慌殉簸,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沽讹,死亡現(xiàn)場離奇詭異般卑,居然都是意外死亡,警方通過查閱死者的電腦和手機爽雄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門蝠检,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人盲链,你說我怎么就攤上這事蝇率。” “怎么了刽沾?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵本慕,是天一觀的道長。 經(jīng)常有香客問我侧漓,道長锅尘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任布蔗,我火速辦了婚禮藤违,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘纵揍。我一直安慰自己顿乒,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布泽谨。 她就那樣靜靜地躺著璧榄,像睡著了一般特漩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上骨杂,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天涂身,我揣著相機與錄音,去河邊找鬼搓蚪。 笑死蛤售,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的妒潭。 我是一名探鬼主播悴能,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼杜耙!你這毒婦竟也來了搜骡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤佑女,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后谈竿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體团驱,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年空凸,在試婚紗的時候發(fā)現(xiàn)自己被綠了嚎花。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡呀洲,死狀恐怖紊选,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情道逗,我是刑警寧澤兵罢,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站滓窍,受9級特大地震影響卖词,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜吏夯,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一此蜈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧噪生,春花似錦裆赵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽页藻。三九已至,卻和暖如春陈醒,著一層夾襖步出監(jiān)牢的瞬間惕橙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工钉跷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留弥鹦,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓爷辙,卻偏偏與公主長得像彬坏,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子膝晾,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

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

  • 在iOS開發(fā)中經(jīng)常會涉及到觸摸事件栓始。本想自己總結(jié)一下,但是遇到了這篇文章血当,感覺總結(jié)的已經(jīng)很到位幻赚,特此轉(zhuǎn)載。作者:L...
    WQ_UESTC閱讀 6,021評論 4 26
  • 好奇觸摸事件是如何從屏幕轉(zhuǎn)移到APP內(nèi)的臊旭?困惑于Cell怎么突然不能點擊了落恼?糾結(jié)于如何實現(xiàn)這個奇葩響應(yīng)需求?亦或是...
    Lotheve閱讀 57,280評論 51 599
  • 本文來自:http://ios.jobbole.com/84081/ 前言: 按照時間順序离熏,事件的生命周期是這樣的...
    HackerOnce閱讀 2,841評論 1 10
  • 一篇搞定事件傳遞佳谦、響應(yīng)者鏈條、hitTest和pointInside的使用發(fā)生觸摸事件后滋戳,系統(tǒng)會將該事件加入到一個...
    克魯?shù)吕?/span>閱讀 1,122評論 0 1
  • 由于高一時候一時沖動競選過副班長钻蔑,然后高二就莫名被人推上了班長的位置。現(xiàn)在看來奸鸯,當(dāng)時班主任確實太年輕啊咪笑,都沒有試用...
    李紹旌閱讀 232評論 0 0