iOS進階:通過實際項目來學(xué)習(xí)掌握響應(yīng)鏈

項目中的問題

在前段時間的項目中漏峰,遇到了一個與響應(yīng)鏈相關(guān)的問題情竹。效果圖如下:


效果1.png
效果2.png

在默認(rèn)狀態(tài)下障陶,最下方有五個按鈕滋恬;當(dāng)點擊選中地圖上的單車后,五個按鈕會同時上移抱究,并且導(dǎo)航視圖也會跟著上移恢氯。如果是你你會如何去實現(xiàn)。
我的第一反應(yīng)就是鼓寺,將這些彼此有約束的按鈕都放在一個自定義視圖上勋拟,這樣,當(dāng)需要上移或者下移的時候妈候,只需要改變這個自定義視圖的frame即可敢靡。但是其實這樣是有問題的。

首先州丹,為了不讓這個自定義視圖遮蓋住下面的地圖醋安,我將該視圖的背景顏色改為clearColor。運行起來后墓毒,界面上沒有問題,但是當(dāng)我在自定義視圖的透明區(qū)域滑動地圖的時候亲怠,發(fā)現(xiàn)地圖不會響應(yīng)我的滑動事件所计。原因是該視圖雖透明,但仍然遮蓋在了地圖上方团秽,所以地圖不會響應(yīng)主胧。雖然我知道原因,但是用戶可不知道习勤,當(dāng)他發(fā)現(xiàn)下面部分不能滑動地圖踪栋,就認(rèn)為是bug了。图毕。

我當(dāng)時的解決辦法

當(dāng)時的我對于響應(yīng)鏈的掌握處于知道是什么夷都,卻不會用的狀態(tài)。由于時間緊迫予颤,我只能采取一個“不太好”的方法囤官。

我創(chuàng)建了一個管理下方視圖的工具類,在工具類初始化的時候傳入控制器的view蛤虐,并在內(nèi)部添加各種按鈕党饮。
這種方法其實和在控制器中添加一個個按鈕沒什么差別,現(xiàn)在只是將添加按鈕的代碼放到了工具類中驳庭,讓控制器的代碼能少點刑顺。

通過響應(yīng)鏈來解決

在使用響應(yīng)鏈之前,得知道響應(yīng)鏈?zhǔn)窃趺垂ぷ鞯摹T诮酉聛淼奈恼轮卸滋茫視葘戫憫?yīng)鏈的相關(guān)知識荞驴,再寫如何用這些相關(guān)知識去解決上面的問題。

什么是響應(yīng)鏈

響應(yīng)者鏈條:在iOS程序中無論是最后面的UIWindow還是最前面的某個按鈕贯城,它們的擺放是有前后關(guān)系的熊楼,一個控件可以放到另一個控件上面或下面,那么用戶點擊某個控件時是觸發(fā)上面的控件還是下面的控件呢能犯,這種先后關(guān)系構(gòu)成一個鏈條就叫“響應(yīng)者鏈”鲫骗。也可以說,響應(yīng)者鏈?zhǔn)怯啥鄠€響應(yīng)者對象連接起來的鏈條踩晶。在iOS中響應(yīng)者鏈的關(guān)系可以用下圖表示:


響應(yīng)鏈?zhǔn)疽鈭D.png

需要注意:

  • 如果當(dāng)前這個view是控制器的view,那么控制器就是上一個響應(yīng)者
  • 如果當(dāng)前這個view不是控制器的view,那么父控件就是上一個響應(yīng)者

上述來源:史上最詳細的iOS之事件的傳遞和響應(yīng)機制-原理篇

響應(yīng)鏈?zhǔn)侨绾喂ぷ鞯模üぷ鞑襟E)

第一步执泰、事件的產(chǎn)生

  • 當(dāng)點擊其中一個視圖的時候,系統(tǒng)會將該事件加入到一個由UIApplication管理的事件隊列中
  • 取出隊列里的最前面事件渡蜻,分發(fā)處理

第二步术吝、事件的傳遞

  • 獲取到需要處理的事件后,順著響應(yīng)鏈茸苇,向下查找最合適的視圖排苍。

第三步、事件的響應(yīng)

  • 找到最合適的視圖后学密,會調(diào)用自己的touches方法處理事件淘衙。如果自身沒有做處理(也就是自身沒有重寫touches方法),那么會逆著響應(yīng)鏈腻暮,向上拋彤守,直到找到能響應(yīng)這個事件的視圖。如果最上頭的UIApplication也不能處理該事件或消息哭靖,則將其丟棄具垫。

實例講解

我用一個簡單的實例來講解。在控制器中试幽,添加若干個視圖筝蚕。如同所示:


實例結(jié)構(gòu)1.png
實例結(jié)構(gòu)2.png

當(dāng)我點擊視圖D的時候,會發(fā)生些什么事情呢抡草。

  • 因為沒有別的事件需要處理饰及,會把這個點擊事件拿出來處理。
  • 開始順著響應(yīng)鏈查找最合適的響應(yīng)視圖 這里就是D視圖
  • D視圖如果能響應(yīng)這個事件就響應(yīng)康震,如果不能上拋燎含,到UIApplication后還是不能就丟棄。

這里有幾個問題需要說明腿短。

這里的響應(yīng)鏈?zhǔn)窃趺礃拥?/h3>

響應(yīng)鏈如下圖所示:


結(jié)構(gòu)圖.png
示例響應(yīng)鏈.png

如何通過這個鏈條找到最合適的視圖

在說這個之間先得知道屏箍,響應(yīng)鏈里的每一個類都是繼承UIResponder绘梦,而該類中有以下兩個方法:

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

當(dāng)在找尋響應(yīng)視圖的時候,會先調(diào)用hitTest方法赴魁,這個方法的用途是返回一個最合適的視圖卸奉。
hitTest方法內(nèi)部調(diào)用pointInside方法,這個方法的作用是點擊的點是否在自身內(nèi)部颖御。

下面以實例說說:(因為UIApplication不好說榄棵,就以View A舉例子)

  • 當(dāng)點擊視圖D以后,會先進入View AhitTest方法來尋找最合適的視圖潘拱。
  • hitTest方法內(nèi)部先判斷是否View A隱藏疹鳄、不能觸發(fā)事件或者透明度<0.01。如果是芦岂,這返回nil瘪弓,說明最合適的視圖不在View A內(nèi)部;反之禽最,繼續(xù)往下腺怯。
  • 調(diào)用pointInside方法,判斷點是否在自身內(nèi)部川无。如果不在內(nèi)部呛占,那么同樣返回nil;反之舀透,說明在View A內(nèi)部栓票,繼續(xù)往下。
  • View A的子視圖中愕够,倒著找。(也就是先找E 再找D中)

這樣可以試著寫出hitTest方法:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    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;
}

上述代碼來源:iOS事件響應(yīng)鏈中Hit-Test View的應(yīng)用

那么示例中佛猛,點擊D視圖后惑芭,是怎么調(diào)用方法的呢?
我重寫了A-E五個視圖的hitTestpointInside方法继找。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    NSLog(@"進入A_View---hitTest withEvent ---");
    UIView * view = [super hitTest:point withEvent:event];
    NSLog(@"離開A_View--- hitTest withEvent ---hitTestView:%@",view);
    return view;
}

- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event {
    NSLog(@"A_view--- pointInside withEvent ---");
    BOOL isInside = [super pointInside:point withEvent:event];
    NSLog(@"A_view--- pointInside withEvent --- isInside:%d",isInside);
    return isInside;
}

(ps:這里只需要調(diào)用super的對應(yīng)方法遂跟,就可以讓重寫沒有影響了。這句話什么意思呢:如果只是在重寫方法中打印數(shù)據(jù)婴渡,那么就不會繼續(xù)往下找了幻锁。而如果調(diào)用了super的對應(yīng)方法,也就是UIView的方法边臼,就會繼續(xù)往下找哄尔,就不會影響到程序了。)

當(dāng)我點擊D后柠并,打印結(jié)果如下:


打印結(jié)果.png

最后找到了D視圖岭接。

找到視圖后的響應(yīng)是怎么回事

再找到視圖后富拗,會調(diào)用這個視圖的touches方法。當(dāng)這個視圖有重寫這些方法的時候鸣戴,就說明這個視圖能響應(yīng)本次事件啃沪,如果這個視圖不能響應(yīng),那么就會順著響應(yīng)鏈上拋窄锅,直到找到能響應(yīng)這個事件的響應(yīng)者创千。

舉個栗子:
還是上面的示例,當(dāng)我不寫D視圖的touches的方法入偷,也就是D視圖不能響應(yīng)事件追驴,那么會將這次響應(yīng)上拋給C視圖,如果我重寫了C視圖的touches方法后盯串,會調(diào)用C視圖的方法氯檐。

響應(yīng)動圖.gif

當(dāng)我點擊D視圖后,調(diào)用的是C視圖的touches体捏,而當(dāng)我點擊E視圖后冠摄,調(diào)用的是E視圖的touches

解決開始的問題

下面就可以來通過響應(yīng)鏈解決開頭的問題了几缭。

最好的思路

我把開頭的需求簡化成了如下:


簡化示意圖.png

如果什么都不操作河泳,只是在tableview上放一個yellow viewyellow view上放兩個按鈕年栓,那么在黃色視圖上滾動tableview是沒有用的拆挥。
原因是這樣的:

  • yellow view視圖上 上下滑動tableview,事件產(chǎn)生并開始處理某抓。
  • 通過響應(yīng)鏈查找最合適的響應(yīng)視圖
    • 先是控制器view纸兔,發(fā)現(xiàn)點擊位置在其內(nèi)部
    • 再是tableview,發(fā)現(xiàn)點擊位置在其內(nèi)部
    • 再是yellow view 發(fā)現(xiàn)點擊位置在其內(nèi)部
    • 最后是兩個按鈕否副,發(fā)現(xiàn)不在他們內(nèi)部汉矿,那么找到最合適的視圖為yellow view
  • 那么由yellow view來響應(yīng)本次事件备禀。

清楚過程后洲拇,只需要在進入yellow viewhitTest方法時,做一下處理曲尸,讓其不是合適的響應(yīng)視圖即可赋续。

具體如下:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView *hitView = [super hitTest:point withEvent:event]; // 找到了最合適的視圖
    if (hitView && hitView == self) { // 當(dāng)找到了并且是自己本身時候,返回nil另患,告訴上一級tableview纽乱,最合適的視圖不在我內(nèi)部
        return nil;
    }
    return hitView;
}

邏輯如下:

  • 進入控制器view,發(fā)現(xiàn)點擊位置在其內(nèi)部柴淘,那么往其子視圖中找
  • 發(fā)現(xiàn)只有tableview迫淹,并且進入發(fā)現(xiàn)點擊位置在tableview中秘通,說明最合適的響應(yīng)視圖要么是tableview,要么是tableview的子視圖敛熬,在往tableview子視圖中找找肺稀。
  • 發(fā)現(xiàn)了yellow view,發(fā)現(xiàn)了點擊位置在yellow view中并且再找了其兩個子視圖按鈕后發(fā)現(xiàn)yellow view正是這個最合適的視圖应民。這個時候如果再不處理话原,系統(tǒng)默認(rèn)會返回yellow view,那么tableview就沒有機會響應(yīng)了诲锹。但是返回了nil繁仁,告訴tableview她渴,沒找到最合適的脾歇,那么tableview就成了最合適的響應(yīng)視圖膘怕。

至于為什么要判斷hitView == self呢橡淑,因為如果這次點擊的是兩個按鈕,那么這里的hitView就是按鈕了闷堡,如果仍然返回nil秒梅,那么按鈕的響應(yīng)也將無法觸發(fā)屋确。

最終效果:


項目解決動圖.gif

另一個思路

這里還有另一個思路桥爽,那就是重寫yellow viewpointInside方法朱灿,因為hitTest內(nèi)部會調(diào)用pointInside來判斷點擊點是否是視圖內(nèi)部,如果不管三七二十一直接返回NO钠四,那么這個視圖將永遠不會作為最合適的視圖盗扒。因此,可以使用這個特性來實現(xiàn)效果:
判斷一下點擊點的位置缀去,如果是兩個按鈕的位置侣灶,就返回YES,如果點擊位置yellow view的黃色區(qū)域缕碎,就返回NO炫隶。

利用這個思路,還可以給小的按鈕增加響應(yīng)熱區(qū)阎曹,給超出父視圖的視圖富裕響應(yīng)能力等。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末煞檩,一起剝皮案震驚了整個濱河市处嫌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌斟湃,老刑警劉巖熏迹,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異凝赛,居然都是意外死亡注暗,警方通過查閱死者的電腦和手機坛缕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捆昏,“玉大人赚楚,你說我怎么就攤上這事∑罚” “怎么了宠页?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長寇仓。 經(jīng)常有香客問我举户,道長,這世上最難降的妖魔是什么遍烦? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任俭嘁,我火速辦了婚禮,結(jié)果婚禮上服猪,老公的妹妹穿的比我還像新娘供填。我一直安慰自己,他們只是感情好蔓姚,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布捕虽。 她就那樣靜靜地躺著,像睡著了一般坡脐。 火紅的嫁衣襯著肌膚如雪泄私。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天备闲,我揣著相機與錄音晌端,去河邊找鬼。 笑死恬砂,一個胖子當(dāng)著我的面吹牛咧纠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播泻骤,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼漆羔,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了狱掂?” 一聲冷哼從身側(cè)響起演痒,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎趋惨,沒想到半個月后鸟顺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡器虾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年讯嫂,在試婚紗的時候發(fā)現(xiàn)自己被綠了蹦锋。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡欧芽,死狀恐怖莉掂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情渐裸,我是刑警寧澤巫湘,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站昏鹃,受9級特大地震影響尚氛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜洞渤,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一阅嘶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧载迄,春花似錦讯柔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至惋耙,卻和暖如春捣炬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背绽榛。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工湿酸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人灭美。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓推溃,卻偏偏與公主長得像,于是被迫代替她去往敵國和親届腐。 傳聞我的和親對象是個殘疾皇子铁坎,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

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

  • 重點參考鏈接: View Programming Guide for iOS https://developer....
    Kevin_Junbaozi閱讀 4,397評論 0 15
  • 好奇觸摸事件是如何從屏幕轉(zhuǎn)移到APP內(nèi)的?困惑于Cell怎么突然不能點擊了犁苏?糾結(jié)于如何實現(xiàn)這個奇葩響應(yīng)需求厢呵?亦或是...
    Lotheve閱讀 56,661評論 51 597
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 29,320評論 8 265
  • 在iOS開發(fā)中經(jīng)常會涉及到觸摸事件。本想自己總結(jié)一下傀顾,但是遇到了這篇文章,感覺總結(jié)的已經(jīng)很到位碌奉,特此轉(zhuǎn)載短曾。作者:L...
    WQ_UESTC閱讀 5,988評論 4 26
  • 1寒砖、通過CocoaPods安裝項目名稱項目信息 AFNetworking網(wǎng)絡(luò)請求組件 FMDB本地數(shù)據(jù)庫組件 SD...
    陽明先生_x閱讀 15,968評論 3 119