iOS 中事件的產(chǎn)生,傳遞和響應(yīng)

事件

在iOS中事件UIEvent主要分為以下3大類:

  • 觸摸事件: 手指觸摸屏幕,點(diǎn)擊,滑動(dòng)等
  • 加速計(jì)事件: 主要由傳感器產(chǎn)生,如微信的搖一搖
  • 遠(yuǎn)程控制事件: 遠(yuǎn)程事件,比如通過藍(lán)牙耳機(jī)切換歌曲

本文以觸摸事件為例,介紹事件的產(chǎn)生,傳遞和響應(yīng).


事件的產(chǎn)生


當(dāng)我們觸摸屏幕時(shí),系統(tǒng)會(huì)自動(dòng)生成一個(gè)觸摸事件并加入到一個(gè)由UIApplication管理的事件隊(duì)列中.
由于隊(duì)列先進(jìn)先出的特性,UIApplication會(huì)從事件隊(duì)列中取出最前面的事件儿礼,并將事件分發(fā)下去以便處理珊豹,通常跨嘉,先發(fā)送事件給應(yīng)用程序的主窗口(keyWindow)职烧。


事件的傳遞

問題: Application 如何知道事件應(yīng)該傳遞給哪個(gè)響應(yīng)者(UIResponder)?
答: 通過Hit-testing 查找事件的最佳響應(yīng)者

Hit-testing(重點(diǎn))

Hit-testing使用反向順序的深度優(yōu)先遍歷,首先從根視圖開始,從父視圖到子視圖遍歷,一旦找到了包含觸摸點(diǎn)的最后一個(gè)視圖,便把這個(gè)視圖作為最佳響應(yīng)者.

下面這張圖顯示了視圖層次結(jié)構(gòu)及在屏幕上繪制的UI的示例即横。


可以看出站楚,View AView B及其子級(jí)View A.2View B.1重疊蝠引。但是单起,由于View BView A更晚添加到MainView上,因此View B及其子視圖呈現(xiàn)在View A及其子View 上劣坊。因此嘀倒,當(dāng)用戶的手指在與View A.2重疊的區(qū)域觸摸View B.1時(shí),應(yīng)通過Hit-testing返回View B.1局冰。如下圖:

遍歷算法首先將hitTest:withEvent:消息發(fā)送到UIWindow测蘑,這是視圖層次結(jié)構(gòu)的根視圖。這個(gè)方法的返回值是最佳響應(yīng)者.
以下流程圖是Hit-testing的實(shí)現(xiàn)邏輯邏輯康二。


以下代碼可能是hitTest:withEvent:的實(shí)現(xiàn)方式.(因?yàn)樘O果不開源,所以我們只能自己推斷)

   func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        
        if !isUserInteractionEnabled || isHidden || alpha <= 0.01 {
            
            return nil
        }
        
        if self.point(inside: point, with: event) {
            
            for subview in subviews {
                
                let convertedPoint = subview.convert(point, from: self)
                if let hitTestView = subview .hitTest(convertedPoint, with: event) {
                    return hitTestView
                }
            }
            
            return self
        }
        
        return nil
    }

這個(gè)方法首先判斷是否允許視圖接收事件碳胳。在這幾種情況下,允許視圖接收事件:

  • 該視圖未隱藏:hidden == NO
  • 該視圖已開啟用戶交互:userInteractionEnabled == YES
  • 該視圖的透明度大于0.01:alpha > 0.01
  • 該視圖包含觸摸點(diǎn):pointInside:withEvent: == YES

如果允許視圖接收事件,則此方法倒序遍歷子視圖(subviews),并給每個(gè)子視圖發(fā)送hitTest:withEvent:消息,直到其中一個(gè)返回非nil值沫勿。如果所以子視圖返回nil,或者沒有子視圖挨约,則返回self。如果不允許視圖接收事件产雹,則此方法將返回nil诫惭,而不會(huì)遍歷子視圖。因此蔓挖,Hit-testing過程可能不會(huì)訪問視圖層次結(jié)構(gòu)中的所有視圖夕土。

當(dāng)找到最佳響應(yīng)者后,application會(huì)把事件傳遞給最佳響應(yīng)者,也就是UIApplication->window->處理事件最合適的view.

事件的處理

在iOS中不是任何對象都能處理事件,只有繼承了UIResponder的對象才能接受并處理事件瘟判,我們稱之為響應(yīng)者對象怨绣。以下都是繼承自UIResponder的角溃,所以都能接收并處理事件。

  • UIApplication
  • UIViewController
  • UIView

問題:那么為什么繼承自UIResponder的類就能夠接收并處理事件呢篮撑?
答:因?yàn)閁IResponder中提供了以下4個(gè)對象方法來處理觸摸事件减细。

// 觸摸事件
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
// 加速計(jì)事件
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event
// 遠(yuǎn)程控制事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event

以UIView為例來說明觸摸事件的處

// UIView是UIResponder的子類,可以覆蓋下列4個(gè)方法處理不同的觸摸事件
// 一根或者多根手指開始觸摸view咽扇,系統(tǒng)會(huì)自動(dòng)調(diào)用view的下面方法
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
// 一根或者多根手指在view上移動(dòng)邪财,系統(tǒng)會(huì)自動(dòng)調(diào)用view的下面方法(隨著手指的移動(dòng),會(huì)持續(xù)調(diào)用該方法)
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
// 一根或者多根手指離開view质欲,系統(tǒng)會(huì)自動(dòng)調(diào)用view的下面方法
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
// 觸摸結(jié)束前树埠,某個(gè)系統(tǒng)事件(例如電話呼入)會(huì)打斷觸摸過程,系統(tǒng)會(huì)自動(dòng)調(diào)用view的下面方法
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
// 提示:touches中存放的都是UITouch對象

需要注意的是:以上四個(gè)方法是由系統(tǒng)自動(dòng)調(diào)用的嘶伟,所以可以通過重寫該方法來處理一些事件怎憋。

  • 如果兩根手指同時(shí)觸摸一個(gè)view,那么view只會(huì)調(diào)用一次touchesBegan:withEvent:方法九昧,touches參數(shù)中裝著2個(gè)UITouch對象
  • 如果這兩根手指一前一后分開觸摸同一個(gè)view绊袋,那么view會(huì)分別調(diào)用2次touchesBegan:withEvent:方法,并且每次調(diào)用時(shí)的touches參數(shù)中只包含一個(gè)UITouch對象
  • 重寫以上四個(gè)方法铸鹰,如果是處理UIView的觸摸事件,必須要自定義UIView子類繼承自UIView癌别。
  • 如果是處理UIViewController的觸摸事件,那么在控制器的.m文件中直接重寫那四個(gè)方法即可蹋笼。

問題: 如果響應(yīng)者沒有處理接受到的事件會(huì)怎么樣呢展姐?
答:通過響應(yīng)者鏈條找到上一個(gè)事件響應(yīng)者,讓它處理剖毯。

響應(yīng)者鏈條
在iOS程序中無論是最后面的UIWindow還是最前面的某個(gè)按鈕圾笨,它們的擺放是有前后關(guān)系的,一個(gè)控件可以放到另一個(gè)控件上面或下面逊谋,那么用戶點(diǎn)擊某個(gè)控件時(shí)是觸發(fā)上面的控件還是下面的控件呢擂达,這種先后關(guān)系構(gòu)成一個(gè)鏈條就叫“響應(yīng)者鏈”。也可以說胶滋,響應(yīng)者鏈?zhǔn)怯啥鄠€(gè)響應(yīng)者對象連接起來的鏈條板鬓。在iOS中響應(yīng)者鏈的關(guān)系可以用下圖表示:


響應(yīng)者鏈的事件傳遞過程:

    1. 如果當(dāng)前view是控制器的view,那么控制器就是上一個(gè)響應(yīng)者镀钓,事件就傳遞給控制器穗熬;如果當(dāng)前view不是控制器的view,那么父視圖就是當(dāng)前view的上一個(gè)響應(yīng)者丁溅,事件就傳遞給它的父視圖
    1. 在視圖層次結(jié)構(gòu)的最頂級(jí)視圖唤蔗,如果也不能處理收到的事件或消息,則其將事件或消息傳遞給window對象進(jìn)行處理
    1. 如果window對象也不處理,則其將事件或消息傳遞給UIApplication對象
    1. 如果UIApplication也不能處理該事件或消息妓柜,則將其丟棄

總結(jié)

    1. 用戶點(diǎn)擊屏幕后產(chǎn)生的一個(gè)觸摸事件箱季,通過Hit-testing,會(huì)找到最合適的視圖控件來處理這個(gè)事件
    1. 找到最合適的視圖控件后棍掐,就會(huì)調(diào)用控件的·touches·方法來作具體的事件處理touchesBegantouchesMovedtouchedEnded
    1. 這些touches方法的默認(rèn)是將事件順著響應(yīng)者鏈條向上傳遞(也就是touch方法默認(rèn)不處理事件藏雏,只傳遞事件),將事件交給上一個(gè)響應(yīng)者進(jìn)行處理
    1. 如果沒有響應(yīng)者處理該事件作煌,則傳遞到UIApplication后會(huì)將事件丟棄掘殴。

問題:如果給一個(gè)視圖添加了點(diǎn)擊手勢,又實(shí)現(xiàn)touchesBegan方法粟誓,那么由誰來處理事件奏寨?
答:根據(jù)事件處理優(yōu)先級(jí),由手勢響應(yīng)處理事件.但是視圖也能接受到touchesBegan事件,因?yàn)槭謩葑R(shí)別是需要一點(diǎn)時(shí)間的,在手勢還是Possible 狀態(tài)的時(shí)候事件傳遞給了響應(yīng)鏈的第一個(gè)響應(yīng)對象,也就是我們的視圖.

注意: 手勢處理完事件之后會(huì)調(diào)用touchesCancelled方法廢棄掉事件.如果視圖是一個(gè)Button,那么這個(gè)ButtonaddTarget:action方法是接收不到事件的.

事件處理優(yōu)先級(jí)

    1. 手勢: 處理完事件之后會(huì)調(diào)用touchesCancelled方法廢棄掉事件.
    1. toucheBegan: 由于手勢處理需要時(shí)間,能在處理手勢之前接收到事件.可以調(diào)用[super toucheBegan:]方法將事件繼續(xù)傳遞給addTarget:action:
    1. addTarget:action: 如果添加了手勢或?qū)崿F(xiàn)了touchesBegan方法則接收不到事件.
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市鹰服,隨后出現(xiàn)的幾起案子病瞳,更是在濱河造成了極大的恐慌,老刑警劉巖悲酷,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件套菜,死亡現(xiàn)場離奇詭異,居然都是意外死亡设易,警方通過查閱死者的電腦和手機(jī)逗柴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來顿肺,“玉大人嚎于,你說我怎么就攤上這事⌒冢” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵袍睡,是天一觀的道長知染。 經(jīng)常有香客問我,道長斑胜,這世上最難降的妖魔是什么控淡? 我笑而不...
    開封第一講書人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮止潘,結(jié)果婚禮上掺炭,老公的妹妹穿的比我還像新娘。我一直安慰自己凭戴,他們只是感情好涧狮,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般者冤。 火紅的嫁衣襯著肌膚如雪肤视。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評(píng)論 1 305
  • 那天涉枫,我揣著相機(jī)與錄音邢滑,去河邊找鬼。 笑死愿汰,一個(gè)胖子當(dāng)著我的面吹牛困后,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播衬廷,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼摇予,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了泵督?” 一聲冷哼從身側(cè)響起趾盐,我...
    開封第一講書人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎小腊,沒想到半個(gè)月后救鲤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡秩冈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年本缠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片入问。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡丹锹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出芬失,到底是詐尸還是另有隱情楣黍,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布棱烂,位于F島的核電站租漂,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏颊糜。R本人自食惡果不足惜哩治,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望衬鱼。 院中可真熱鬧业筏,春花似錦、人聲如沸鸟赫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至翠勉,卻和暖如春妖啥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背对碌。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來泰國打工荆虱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人朽们。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓怀读,卻偏偏與公主長得像,于是被迫代替她去往敵國和親骑脱。 傳聞我的和親對象是個(gè)殘疾皇子菜枷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

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