iOS事件傳遞:響應者鏈[譯]

事件傳遞:響應者鏈

當你設計一個app的時候,你很可能需要你的app能夠動態(tài)響應某些事件。比如,觸摸可以發(fā)生在屏幕上不同對象上,你需要決定哪些對象來響應一個特定的事件,并了解對象是如何接收事件。

當一個用戶事件產生的時候,UIKit創(chuàng)建一個事件對象,這個對象包含了如何處理這個事件的信息。然后將這個事件對象放到這個活躍app的事件隊列中。對于觸摸事件,這些對象都到包裹成了UIEvent對象。對于運動事件,取決于你使用的框架和你感興趣的運動事件的類型。

一個事件沿著一個特定的路徑,知道它被傳遞到一個能處理這個事件的對象。首先,事件最先被UIApplication單例對象獲取,這個對象還負責這個對象的分發(fā)。典型的,這事件被傳遞到app的主窗口對象,也即使把時間傳遞給一個初始對象來處理。初始對象取決于事件的類型。

  • 觸摸事件
    對于觸摸事件,主窗口對象首先嘗試把事件傳遞給觸摸發(fā)生的視圖對象上。這個對象是一個hit-test視圖對象。找到hit-test視圖對象的過程叫hit-testing。下面會詳細說明:
  • 運動事件和遠程事件
    對于這兩類事件,主窗口對象把事件發(fā)送給第一響應者。

最終的目標是找到一個對象能處理這個事件和響應這個事件。因此,UIkit對象首先把事件發(fā)送給最適合處理這個事件的對象。對于觸摸事件,這個對象是hit-test視圖,對于其他的事件,這個對象是第一響應者。接下來的部分會詳細說明hit-test視圖和 第一響應者是如何被決定的。

Hit-Testing 返回觸摸發(fā)生的視圖

iOS使用hit-testing來尋找被觸摸的視圖。Hit-testing包括檢查一個觸摸是否發(fā)生在相關的視圖對象上。如果是,就遞歸的去檢查這個視圖的所有子視圖。最底層視圖包含了觸摸點的視圖成為hit-test視圖。在知道hit-test視圖之后,事件交給這個視圖來處理。

`Hit-testing`返回被觸摸的子視圖
`Hit-testing`返回被觸摸的子視圖

說明:假設用戶觸摸了視圖E在上圖中。iOS通過以下檢查子視圖的順序來找到hit-test視圖:

  • 1.觸摸事件發(fā)生在視圖A的邊界內。所以檢測它的子視圖B和C。
  • 2.觸摸沒有發(fā)生在B,但是發(fā)生在C內。所以檢測C的子視圖D和E。
  • 3.觸摸沒有發(fā)生在D,但是發(fā)生在E內。

視圖E是包含觸摸點堆棧視圖上最底層的視圖。所以視圖E成為hit-test視圖。
方法 hitTest:withEvent:根據一個給定的CGPointUIEvent返回指定的hit-test視圖。hitTest:withEvent:方法首先調用pointInside:withEvent:開始,如果傳遞到hitTest:withEvent:的點在這個視圖的bounds內,那么pointInside:withEvent:返回YES。繼而,在返回YES的所有子view上遞歸調用hitTest:withEvent:。

如果傳遞hitTest:withEvent:的點不在視圖的邊界內,第一次調用pointInside:withEvent:方法會返回NO,這個點會被忽略,hitTest:withEvent:方法返回nil.如果一個子視圖返回NO。那么這個子視圖的所有堆棧上的視圖全部被忽略。因為觸摸沒有發(fā)生在這個子視圖上,就更不可能發(fā)生在其子視圖的子視圖上了。這意味著如果一個子視圖超出了其父視圖的邊界,則超出邊界的部分是不會響應事件。這部分父視圖都無法接收時間,更不用說往下傳遞了。如果子視圖的clipsToBounds屬性被設置為NO。

注意:一個觸摸事件在其生命周期內,跟這個hit-test視圖綁定,即便這個觸摸事件滑出了當前視圖。

這個hit-test視圖被給予第一優(yōu)先權處理這個觸摸事件。如果這個視圖不能處理這個事件,這個觸摸事件順著響應者鏈向上傳遞,直到找到第一個能處理這個事件的對象。

響應者鏈是由一系列響應者連成的鏈

很多類型的事件的傳遞都依賴于響應者鏈。響應者鏈是一系列連接在一起的響應者對象。它從第一響應者開始,以application對象結束。如果第一響應者不能處理這個事件,它會把這個事件沿著這個響應者鏈傳遞到下一個響應者。

一個響應者對象是一個能響應并能處理事件的對象。UIResponder類是所有響應者的父類,它定義了事件處理和常見響應者行為的通用編程接口。UIApplication,UIViewController以及UIView類的實例對象都是響應者,這表明,所有視圖和絕大多數主控制器都是響應者。需要注意的是核心動畫的圖層對象不是響應者

第一響應者被指定為首先接收事件的對象。通常,第一響應者是一個視圖對象。一個對象要成為第一響應者,需要滿足下面兩個條件:

  • 1.重寫canBecomeFirstResponder方法并返回YES。
  • 2.接收becomeFirstResponder消息。如果有必要,一個對象可以給自己發(fā)送這個消息。

注意:在指定一個對象為第一響應者之前,要確保這個對象在其合適的生命周期內。比如,通常在重寫的viewDidAppear:方法中調用becomeFirstResponder方法。如果你試圖在viewWillAppear:給一個第一響應者賦值,由于對象還沒有創(chuàng)建完成。那么becomeFirstResponder方法會返回NO。

事件并不是唯一依賴于響應者鏈的對象。響應者鏈在以下處理中都會用到:

  • 觸摸事件 如果hit-test視圖不能處理這個觸摸事件,則從這個視圖開始順著響應者鏈向上級傳遞。
  • 運動事件 UIKit框架的對象如果要處理搖晃事件,第一響應者必須要實現UIResponder類的motionBegin:withEvent:motionEnded:withEvent:方法。
  • 遠程事件 如果要處理遠程事件,第一響應者比如要實現UIResponder類的remoteControlReceivedWithEvent:方法。
  • Action消息 當用戶在操縱一個控件時,例如一個按鈕或者一個開關,這個action方法的target為空,這個消息會順著響應者鏈從第一響應者(可能是這個控件自身)往后傳遞。
  • 快捷菜單消息 當用戶點擊了快捷菜單的菜單時,iOS使用響應者鏈來找到一個實現了必要方法的對象來處理。(實現cut:, copy:, paste:)
  • 文本編輯 當用戶點擊一個textField或者textView,則這個文本控件自動變成第一響應者。默認的響應是彈出虛擬鍵盤,這個文本控件獲得焦點,可以開始編輯。你還可以根據應用的需要自定義鍵盤。你還可以給任何響應者對象添加自定義的輸入控件。

UIKit自動將用戶點擊的文本控件設置為第一響應者,其他對象,則需要顯示的調用becomeFirstResponder方法來成為第一響應者。

響應者鏈的路徑

如果初始對象,hit-test視圖或者第一響應者不能處理事件。UIKit把事件傳遞給響應者鏈上的下一個響應者。每一個響應者決定是否處理這個事件或者是把這個事件繼續(xù)傳遞給下一個響應者,使用nextResponder方法。直到找到一個響應者或再也沒有別的響應者。一個響應者序列,從iOS檢測到事件并傳遞給初始對象開始,通常是一個視圖。這個初始視圖有第一優(yōu)先權來處理這個事件。下圖顯示了兩種事件傳遞路徑對于不同的應用配置。一個應用的事件傳遞路徑由其具體的結構決定,但遵循著同樣的規(guī)律。

iOS響應者鏈
iOS響應者鏈

對于左圖,事件傳遞路線

  • 1.initial view試圖處理這個事件或消息。如果它不能處理這個事件,它把事件傳遞給它的父視圖,因為initial view不是控制器的根視圖。
  • 2.父視圖試圖處理這個事件。如果它也不能處理這個事件,則繼續(xù)傳遞給它的父視圖,因為當前視圖還不是控制器的根視圖。
  • 3.控制器的根視圖試圖處理這個事件,如果根視圖還不能處理這個事件,則把事件傳遞給它的控制器。
  • 4.控制器視圖處理這個事件,如果不能,則把事件傳遞給窗口對象。
  • 5.如果窗口對象還不能處理它,則把事件傳遞給單例的application對象。
  • 6.如果單例的application對象也不能處理,則事件被忽略。

右邊的情形有點稍微的不同,但都遵循著以下規(guī)律:

  • 1.事件向上傳遞,直到遇到某個控制器的根視圖。
  • 2.根視圖把事件傳遞給它的控制器。
  • 3.控制器接著把事件傳遞給包含它的視圖,一直傳遞到另一層根視圖。1~3步重復,直到找到根控制器。
  • 4.根控制器把事件傳遞給窗口對象。
  • 5.窗口對象把事件傳遞給application對象。

重要:如果你實現一個自定義視圖來處理遠程事件,action消息,UIKit運動事件,快捷菜單消息。不要直接把事件或者消息傳遞給下一個響應者。取而代之的是,你應該先調用父類的實現,讓UIKit為你處理響應者鏈的遍歷。

譯自:Event Delivery: The Responder Chain

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容