iOS 中的事件傳遞和響應(yīng)機制 - 原理篇

注:根據(jù)史上最詳細的iOS之事件的傳遞和響應(yīng)機制-原理篇重新整理(適當(dāng)刪減及補充)。

在 iOS 中,只有繼承了 UIReponder(響應(yīng)者)類的對象才能接收并處理事件畦戒。其公共子類包括 UIViewUIViewControllerUIApplication

UIReponder 類中提供了以下 4 個對象方法來處理觸摸事件:

/// 觸摸開始
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {}
/// 觸摸移動
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {}
/// 觸摸取消(在觸摸結(jié)束之前)
/// 某個系統(tǒng)事件(例如電話呼入)會打斷觸摸過程
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {}
/// 觸摸結(jié)束
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {}

注意:

  • 如果手指同時觸摸屏幕棕兼,touches(_:with:) 方法只會調(diào)用一次,Set<UITouch> 包含兩個對象抵乓;

  • 如果手指前后觸摸屏幕伴挚,touches(_:with:) 會依次調(diào)用,且每次調(diào)用時 Set<UITouch> 只有一個對象灾炭。

iOS 中的事件傳遞

事件傳遞和響應(yīng)的整個流程

  1. 觸發(fā)事件后茎芋,系統(tǒng)會將該事件加入到一個由 UIApplication 管理的事件隊列中;
  2. UIApplication 會從事件隊列中取出最前面的事件蜈出,將之分發(fā)出去以便處理田弥,通常,先發(fā)送事件給應(yīng)用程序的主窗口( keyWindow )铡原;
  3. 主窗口會在視圖層次結(jié)構(gòu)中<u>找到一個最適合的視圖</u>來處理觸摸事件偷厦;
  4. 找到適合的視圖控件后商叹,就會調(diào)用該視圖控件的 touches(_:with:) 方法;
  5. touches(_:with:) 的默認實現(xiàn)是將事件順著響應(yīng)者鏈(后面會說)一直傳遞下去只泼,直到連 UIApplication 對象也不能響應(yīng)事件剖笙,則將其丟棄。

如何尋找最適合的控件來處理事件

當(dāng)事件觸發(fā)后辜妓,系統(tǒng)會調(diào)用控件的 hitTest(_:with:) 方法來遍歷視圖的層次結(jié)構(gòu)枯途,以確定哪個子視圖應(yīng)該接收觸摸事件,過程如下:

  1. 調(diào)用自己的 hitTest(_:with:) 方法籍滴;
  2. 判斷自己能否觸發(fā)事件酪夷、是否隱藏、alpha <= 0.01孽惰;
  3. 調(diào)用 point(inside:with:) 來判斷觸摸點是否在自己身上晚岭;
  4. 倒序遍歷 subviews ,并重復(fù)前面三個步驟勋功。直到找到包含觸摸點的最上層視圖坦报,并返回這個視圖,那么該視圖就是那個最適合的處理事件的 view狂鞋;
  5. 如果沒有符合條件的子控件片择,就認為自己最適合處理事件,也就是自己是最適合的 view骚揍;

通俗一點來解釋就是字管,其實系統(tǒng)也無法決定應(yīng)該讓哪個視圖處理事件,那么就用遍歷的方式信不,依次找到包含觸摸點所在的最上層視圖嘲叔,則認為該視圖最適合處理事件。

注意:

觸摸事件傳遞的過程是從父控件傳遞到子控件的抽活,如果父控件也不能接收事件硫戈,那么子控件就不可能接收事件。

尋找最適合的的 view 的底層剖析

  • hitTest(_:with:) 的調(diào)用時機

    • 事件開始產(chǎn)生時會調(diào)用下硕;
    • 只要事件傳遞給一個控件丁逝,就會調(diào)用這個控件的 hitTest(_:with:) 方法(不管這個控件能否處理事件或觸摸點是否自己身上)。
  • hitTest(_:with:) 的作用

    返回一個最適合的 view 來處理觸摸事件卵牍。

注意:

如果 hitTest(_:with:) 方法中返回 nil 果港,那么該控件本身和其 subview 都不是最適合的 view,而是該控件的父控件糊昙。

在默認的實現(xiàn)中,如果確定最終父控件是最適合的 view谢谦,那么仍然會調(diào)用其子控件的 hitTest(_:with:) 方法(不然怎么知道有沒有更適合的 view释牺?參考 如何尋找最適合的控件來處理事件萝衩。)

hitTest(_:with:) 的默認實現(xiàn)

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    // 1. 判斷自己能否觸發(fā)事件
    if !self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01 {
        return nil
    }
    // 2.判斷觸摸點是否在自己身上
    if !self.point(inside: point, with: event) {
        return nil
    }
    // 3. 倒序遍歷 `subviews` ,并重復(fù)前面兩個步驟没咙;
    // 直到找到包含觸摸點的最前面的視圖猩谊,并返回這個視圖,那么該視圖就是那個最合適的接收事件的 view祭刚;
    for view in  subviews.reversed() {
        // 把坐標(biāo)轉(zhuǎn)換成控件上的坐標(biāo)
        let p = self.convert(point, to: view)
        if let hitView = view.hitTest(p, with: event) {
            return hitView
        }
    }
    
    return self
}

iOS 中的事件響應(yīng)

找到最適合的 view 接收事件后牌捷,如果不重寫實現(xiàn)該 view 的 touches(_:with:) 方法,那么這些方法的默認實現(xiàn)是將事件順著響應(yīng)者鏈向下傳遞涡驮, 將事件交給下一個響應(yīng)者去處理暗甥。

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

可以說,響應(yīng)者鏈?zhǔn)怯啥鄠€響應(yīng)者對象鏈接起來的鏈條捉捅。UIReponder 的一個對象屬性 next 能夠很好的解釋這一規(guī)則撤防。

UIReponder().next

返回響應(yīng)者鏈中的下一個響應(yīng)者,如果沒有下一個響應(yīng)者棒口,則返回 nil 寄月。

例如,UIView 調(diào)用此屬性會返回管理它的 UIViewController 對象(如果有)无牵,沒有則返回它的 superview漾肮;UIViewController 調(diào)用此屬性會返回其視圖的 superviewUIWindow 返回應(yīng)用程序?qū)ο缶セ伲还蚕淼?UIApplication 對象則通常返回 nil 克懊。

例如,我們可以通過 UIViewnext 屬性找到它所在的控制器:

extension UIView {
    var next = self.next
    while next != nil { // 符合條件就一直循環(huán)
        if let viewController = next as? UIViewController {
            return viewController
        }
        // UIView 的下一個響應(yīng)控件充岛,直到找到控制器保檐。
        next = next?.next   
    }
    return nil
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市崔梗,隨后出現(xiàn)的幾起案子夜只,更是在濱河造成了極大的恐慌,老刑警劉巖蒜魄,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扔亥,死亡現(xiàn)場離奇詭異,居然都是意外死亡谈为,警方通過查閱死者的電腦和手機旅挤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伞鲫,“玉大人粘茄,你說我怎么就攤上這事。” “怎么了柒瓣?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵儒搭,是天一觀的道長。 經(jīng)常有香客問我芙贫,道長搂鲫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任磺平,我火速辦了婚禮魂仍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘拣挪。我一直安慰自己擦酌,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布媒吗。 她就那樣靜靜地躺著仑氛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪闸英。 梳的紋絲不亂的頭發(fā)上锯岖,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機與錄音甫何,去河邊找鬼出吹。 笑死,一個胖子當(dāng)著我的面吹牛辙喂,可吹牛的內(nèi)容都是我干的捶牢。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼巍耗,長吁一口氣:“原來是場噩夢啊……” “哼秋麸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起炬太,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤灸蟆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后亲族,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體炒考,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年霎迫,在試婚紗的時候發(fā)現(xiàn)自己被綠了斋枢。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡知给,死狀恐怖瓤帚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤缘滥,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布轰胁,位于F島的核電站谒主,受9級特大地震影響朝扼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜霎肯,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一擎颖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧观游,春花似錦搂捧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至搪柑,卻和暖如春聋丝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背工碾。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工弱睦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人渊额。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓况木,卻偏偏與公主長得像,于是被迫代替她去往敵國和親旬迹。 傳聞我的和親對象是個殘疾皇子火惊,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,446評論 2 348

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