Hit-test 實戰(zhàn)

在這篇文章里句占,我們將探討如何使用 hitTest:withEvent:方法來自定義view的響應區(qū)域.

拋出問題

項目中需要實現(xiàn)實時自動補全用戶輸入的功能纪蜒,也就是說在 textField (用戶輸入框)下方需要實時展示出根據(jù)用戶輸入而自動補全的人名列表或者其他列表.如下圖所示:

Image1.png

整個viewcontroller是被一個UICollectionView填充的澄干,在該UICollectionView上有一個touch事件吊洼,用來dismiss keyboard强法。而整個的輸入框view(包含背景)都是添加在UICollectionView中的supplementary view(header view, section equals 1)蟹肘,所以,當autoCompletionTableview(自動補全列表)出現(xiàn)時楣导,會超出其superview的邊界(由于未設置clipToBounds=YES废境,所以超出部分可以看到)。雖然超出的部分可見筒繁,但是去無法正常響應tableview的touch事件噩凹。那如何解決這個問題呢?

原理分析

知己知彼毡咏,百戰(zhàn)不殆驮宴。我們需要了解他的原理,才能對癥下藥呕缭。

在iOS系統(tǒng)中堵泽,存在一個名為“Response chain”(響應者鏈條)的過程. 通俗的解釋就是:系統(tǒng)會在視圖層次結(jié)構(gòu)中找到一個最合適的視圖來處理觸摸事件(pan, pinch, tap, etc). 那系統(tǒng)是如何去尋找的呢?那些方法起了作用呢恢总?

首先迎罗,在響應者鏈條中的“候選者”都會直接或間接的繼承 UIResponder 這個基類,以確保他們的實例可以響應和處理用戶touch事件片仿,例如我們耳熟能詳?shù)?UIApplication纹安、 UIViewController、UIWindow和所有繼承自UIView的UIKit類. 下圖展示了響應者鏈的基本構(gòu)成:

Image2.png

從圖中可以看出響應者鏈有如下特征:

  1. 響應者鏈通常是由視圖(UIView)和控制器(view controller)構(gòu)成的;
  2. 一個視圖的下一個響應者是它視圖控制器(view controller(如果有的話),然后再轉(zhuǎn)給它的父視圖(Super View);
  3. 如果遍歷完所有view和view controller后依舊沒有發(fā)現(xiàn)可響應的組件, 那么單例的內(nèi)容視圖UIWindow將作為下一個響應者;
  4. 最后UIApplication這個"上帝類"將作為響應者鏈的終點結(jié)束整個循環(huán).

事件分發(fā)機制

上一部分是"自底向上"的響應機制, 下面我們來說說"自頂向下"的分發(fā)機制.

整個的響應開端是從UIApplication控制的NSRunloop開始的钻蔑,NSRunloop監(jiān)聽到用戶的touch event(存放在UIApplication的事件隊列中)之后啥刻,就開始了消息的分發(fā), UIWindow是第一個接受這個消息的對象,并以消息的形式將事件發(fā)送給第一響應者咪笑,使其有機會首先處理事件可帽。如果第一響應者沒有進行處理,系統(tǒng)就將事件(通過消息)傳遞給響應者鏈中的下一個響應者窗怒,看看它是否可以進行處理映跟。在這個過程中,UIWindow是通過hitTest:withEvent:方法尋找此次Touch操作初始點所在的視圖(View), 即需要將觸摸事件傳遞給其處理的視圖扬虚,這個過程稱之為hit-test view努隙。

hitTest:withEvent:方法處理流程如下:

首先調(diào)用當前視圖的pointInside:withEvent:方法判斷觸摸點是否在當前視圖內(nèi);之后若返回NO辜昵,則hitTest:withEvent:返回NO荸镊,結(jié)束循環(huán); 若為YES,則向當前視圖的所有子視圖(subviews)發(fā)送hitTest:withEvent:消息堪置,所有子視圖的遍歷順序是從最頂層視圖一直到到最底層視圖躬存,即從subviews數(shù)組的末尾向前遍歷,直到有子視圖返回非空對象或者全部子視圖遍歷完畢舀锨;若第一次有子視圖返回非空對象岭洲,則hitTest:withEvent:方法返回此對象,處理結(jié)束坎匿;如所有子視圖都返回空盾剩,則hitTest:withEvent:方法返回自身(self)。

下面用一個例子說明此流程:

Image3.png

用戶點擊View E替蔬,hit-test view流程如下:

  1. A是UIWindow的根視圖告私,因此,UIWindow對象會首先對A進行hit-test进栽;

  2. 顯然用戶點擊的范圍是在A的范圍內(nèi)德挣,因此,pointInside:withEvent:返回了YES快毛,這時會繼續(xù)檢查A的子視圖;

  3. 這時候會有兩個分支番挺,B和C, 點擊的范圍不再B內(nèi)唠帝,因此B分支的pointInside:withEvent:返回NO,對應的hitTest:withEvent:返回nil玄柏;

  4. 點擊的范圍在C內(nèi)襟衰,即C的pointInside:withEvent:返回YES;這時候有D和E兩個分支:點擊的范圍不再D內(nèi)粪摘,因此D的pointInside:withEvent:返回NO瀑晒,對應的
    hitTest:withEvent:返回nil绍坝;點擊的范圍在E內(nèi),即E的pointInside:withEvent:返回YES苔悦,由于E沒有子視圖(也可以理解成對E的子視圖進行hit-test時返回
    了nil)轩褐,因此,E的hitTest:withEvent:會將E返回玖详,再往回回溯把介,就是C的hitTest:withEven:t方法; 之后返回A的hitTest:withEvent:方法。

至此蟋座,本次點擊事件的第一響應者就通過響應者鏈的事件分發(fā)邏輯成功的找到了拗踢。而且不難看出,這個處理流程有點類似Binary Search的思想向臀,這樣能以最快的速度巢墅,最精確地定位出能響應觸摸事件的UIView。

實戰(zhàn)

在知道了原理后, 我們就要著手解決文章一開始提出的問題了. 問題的關(guān)鍵就在于override hitTest:withEvent 和 pointInside:withEvent: 這兩個方法券膀。
當點擊超出部分的table view區(qū)域時, 首先君纫,其super view 的pointInside:withEvent: 方法會返回NO, 這也直接導致 hitTest:withEvent 返回nil給上層三娩。解決方案可以很簡單, 找到對應的view庵芭,重寫pointInside:withEvent:方法(計算超出部分區(qū)域,判斷后返回YES雀监;或者直接返回YES)双吆。之后, 再重寫hitTest:withEvent:, 因為其默認返回值是包含autoCompletionTableView的container view,并不是table view会前, 所以在這里要轉(zhuǎn)換坐標系好乐,并將返回的view改為超出super view邊界的table view。按照這個步驟瓦宜,超出部分的view就可以響應touch事件了.

代碼如下:

-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
  CGPoint hitPoint = [self.autoCompletionTableView convertPoint:point fromView:self];

  if ([self.autoCompletionTableView pointInside:hitPoint withEvent:event]) {
    return self.autoCompletionTableView;
  }

  return [super hitTest:point withEvent:event];
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蔚万,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子临庇,更是在濱河造成了極大的恐慌反璃,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件假夺,死亡現(xiàn)場離奇詭異淮蜈,居然都是意外死亡,警方通過查閱死者的電腦和手機已卷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門梧田,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事裁眯○睦妫” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵穿稳,是天一觀的道長存皂。 經(jīng)常有香客問我,道長司草,這世上最難降的妖魔是什么艰垂? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮埋虹,結(jié)果婚禮上猜憎,老公的妹妹穿的比我還像新娘。我一直安慰自己搔课,他們只是感情好胰柑,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著爬泥,像睡著了一般柬讨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上袍啡,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天踩官,我揣著相機與錄音,去河邊找鬼境输。 笑死蔗牡,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的嗅剖。 我是一名探鬼主播辩越,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼信粮!你這毒婦竟也來了黔攒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤强缘,失蹤者是張志新(化名)和其女友劉穎督惰,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體旅掂,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡姑丑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了辞友。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖称龙,靈堂內(nèi)的尸體忽然破棺而出留拾,到底是詐尸還是另有隱情,我是刑警寧澤鲫尊,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布痴柔,位于F島的核電站,受9級特大地震影響疫向,放射性物質(zhì)發(fā)生泄漏咳蔚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一搔驼、第九天 我趴在偏房一處隱蔽的房頂上張望谈火。 院中可真熱鬧,春花似錦舌涨、人聲如沸糯耍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽温技。三九已至,卻和暖如春扭粱,著一層夾襖步出監(jiān)牢的瞬間舵鳞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工琢蛤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蜓堕,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓虐块,卻偏偏與公主長得像俩滥,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子贺奠,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

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