10.iOS事件的傳遞與響應(yīng)

Question1: 在UIWindow中添加了兩個ViewController,并顯示后一個ViewController的視圖,結(jié)果視圖并沒有被旋轉(zhuǎn)成橫版,仍舊按照豎版來顯示?

解決辦法:

給UIWindow設(shè)置一個rootViewController,之后添加的所有ViewController都以rootViewController的subview形式添加。

原因:

官方的Q&A講的很簡單:如果往一個UIWindow里面添加了兩個以上的view,那么后面添加的view就會收不到旋轉(zhuǎn)的事件,于是無法正常調(diào)整視圖的方向 —– 只有第一個加入到UIWindow的view才會進行旋轉(zhuǎn)。

Question2: 發(fā)生觸摸事件后,事件的傳遞與響應(yīng)的過程?

1、事件的傳遞,即尋找第一響應(yīng)者

發(fā)生觸摸事件后,系統(tǒng)首先會將該事件加入到一個由UIApplication管理的隊列事件中;
UIApplication會從事件隊列中取出最前面的事件,并將事件分發(fā)下去以便處理,通常會先發(fā)送事件給應(yīng)用程序的主窗口(keyWindow),主窗口會在視圖層次結(jié)構(gòu)中找到一個最合適的視圖來處理觸摸事件。

即:>UIApplication -> Window ->遞歸找到處理事件的控件(尋找hit-test view)

2、事件的響應(yīng),即尋找事件的處理者

遞歸找到處理事件的控件->控件調(diào)用touches方法 -> 判斷是否實現(xiàn)了touch 方法 ->如果沒有實現(xiàn)默認(rèn)將事件傳遞給上一個響應(yīng)者

接下來就是事件的響應(yīng)者對事件進行處理(調(diào)用touches方法) 。

如何做到一個事件多個對象處理:
因為系統(tǒng)默認(rèn)做法是把事件上拋給父控件,所以可以通過重寫自己的touches方法和父控件的touches方法來達(dá)到一個事件多個對象處理的目的。
解釋: 如果需要自定義觸摸事件就需要重寫touch事件,如果找到第一響應(yīng)者重寫這個事件,那么這個事件就由這個響應(yīng)者處理,如果需要這個事件多個對象處理,那么在touch事件里面加上[super touchesBegan:touches withEvent:event];

想要弄清楚具體的過程,讓我們先來看看一些基本的概念

  • 1.在iOS系統(tǒng)中,一共有三種形式的事件 ( 事件類型 )
  1. 觸摸事件(Touch Event) : 當(dāng)用戶觸摸屏幕時發(fā)生的事件。
  2. 運動事件(Motion Event) :用戶移動設(shè)備時發(fā)生的事件:加速計,重力感應(yīng)。
  3. 遠(yuǎn)端控制事件(Remote-control Event) : 如通過耳機進行控制iOS設(shè)備聲音等都屬于遠(yuǎn)端控制事件。
UITouch

當(dāng)你用一根手指觸摸屏幕時, 會創(chuàng)建一個與之關(guān)聯(lián)的UITouch對象, 一個UITouch對象對應(yīng)一根手指. 在事件中可以根據(jù)NSSet中UITouch對象的數(shù)量得出此次觸摸事件是單指觸摸還是雙指多指等等

觸摸產(chǎn)生時所處的窗口
@property(nonatomic,readonly,retain) UIWindow *window;
觸摸產(chǎn)生時所處的視圖
@property(nonatomic,readonly,retain) UIView   *view;
短時間內(nèi)點按屏幕的次數(shù),可以根據(jù)tapCount判斷單擊、雙擊或更多的點擊
@property(nonatomic,readonly) NSUInteger      tapCount;
記錄了觸摸事件產(chǎn)生或變化時的時間,單位是秒
@property(nonatomic,readonly) NSTimeInterval  timestamp;
當(dāng)前觸摸事件所處的狀態(tài)
@property(nonatomic,readonly) UITouchPhase    phase;

UIEvent

每產(chǎn)生一個事件, 就對應(yīng)產(chǎn)生一個UIEvent. UIEvent記錄著該事件產(chǎn)生的時間, 事件的類型等等

UIEvent幾個重要的屬性 :

事件類型
@property(nonatomic,readonly) UIEventType     type;
@property(nonatomic,readonly) UIEventSubtype  subtype;
事件產(chǎn)生的時間
@property(nonatomic,readonly) NSTimeInterval  timestamp;

響應(yīng)者對象(UIResponder)

在iOS中不是任何對象都能處理事件, 只有繼承了UIResponder的對象才能接收并處理事件,我們稱為響應(yīng)者對象
UIApplication,UIViewController,UIView都繼承自UIResponder,因此他們都是響應(yīng)者對象, 都能夠接收并處理事件

繼承自UIResponder的類能處理事件是由于UIResponder內(nèi)部提供了以下方法

觸摸事件
- (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;

加速計事件
- (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相關(guān)屬性:

multipleTouchEnabled:是否開啟多點觸控
exclusiveTouch :多個控件接受事件時的排他性
  • iOS系統(tǒng)事件傳遞


    7.png

當(dāng)用戶發(fā)起一個事件,比如觸摸屏幕或者晃動設(shè)備,系統(tǒng)產(chǎn)生一個事件,同時投遞給UIApplication,而UIApplication則將這個事件傳遞給特定的UIWindow進行處理(正常情況都一個程序都只有一個UIWindow),然后由UIWindow將這個事件傳遞給特定的對象(即first responder)。

事件的傳遞過程中不同事件first responder響應(yīng)者的確定?

雖然都是通過響應(yīng)鏈對事件進行處理,但是觸摸事件和運動事件在處理上有著明顯的不同 ( 主要體現(xiàn)在確定哪個對象才是他們的 first responder ):

觸摸事件是通過HitTest來確定first responder(整個過程和Windows中對消息的處理基本是一樣的):當(dāng)一個事件發(fā)生時,UIWindow將這個事件傳遞給當(dāng)前可見的最頂端的view進行hitTest,并在這個hitTest里面進行遞歸查找,直到找到能夠響應(yīng)hitTest的最底層的那個Responder,確定為first responder。然后從這個responder開始進行處理這個事件,如果不能處理,則往上冒泡直到有一個Responder可以對這個事件進行處理為止。

但是運動事件卻不太一樣,它并不用進行HitTest,而是直接以響應(yīng)鏈中被指定為first responder的對象為起點,通過響應(yīng)鏈進行事件的分發(fā)和處理。第一個加入到UIWindow中的ViewController即是運動事件的first responder。這也就解釋了為啥后加入的view不會被正常的旋轉(zhuǎn):雖然都是通過first responder開始分發(fā)事件,但是一個有進行hittest,一個沒有,雖然大多數(shù)情況下hittest view和first responder是同一個view,但也不絕對。正如旋轉(zhuǎn)的這個例子一樣。

  • 尋找hit-test view

什么是hit-test view呢?簡單來說就是你觸發(fā)事件所在的那個View,尋找hit-test view的過程就叫做Hit-Testing。那么,系統(tǒng)是如何來執(zhí)行Hit-Testing呢,首先假設(shè)現(xiàn)在有如下這么一個UI布局,一種有ABCDE五個View。

屏幕快照 2017-05-26 15.51.36.png

假設(shè)一個單擊事件發(fā)生在了View D里面,系統(tǒng)首先會從最頂層的View A開始尋找,發(fā)現(xiàn)事件是在View A或者其子類里面,那么接著從B和C找,發(fā)現(xiàn)事件是在C或者其子類里面,那么接著到C里面找,這時發(fā)現(xiàn)事件是在D里面,并且D已經(jīng)沒有子類了,那么hit-test view就是View D啦。

  • 第一響應(yīng)者(First Responder)
10.png

第一響應(yīng)者是第一個接收事件的View對象,我們在Xcode的Interface Builder畫視圖時,可以看到視圖結(jié)構(gòu)中就有First Responder。

這里的First Responder就是UIApplication了。另外,我們可以控制一個View讓其成為First Responder,通過實現(xiàn) canBecomeFirstResponder方法并返回YES可以使當(dāng)前View成為第一響應(yīng)者,或者調(diào)用View的becomeFirstResponder方法也可以,例如當(dāng)UITextField調(diào)用該方法時會彈出鍵盤進行輸入,此時輸入框控件就是第一響應(yīng)者。

事件的響應(yīng)之 - 事件的響應(yīng)

9.png
  1. 左邊的情況,接收事件的initial view如果不能處理該事件并且她不是頂層的View,則事件會往它的父View進行傳遞。initial view的父View獲取事件后如果仍不能處理,則繼續(xù)往上傳遞,循環(huán)這個過程。如果頂層的View還是不能處理這個事件的話,則會將事件傳遞給它們的ViewController,如果ViewController也不能處理,則傳遞給Window(UIWindow),此時Window不能處理的話就將事件傳遞給Application(UIApplication),最后如果連Application也不能處理,則廢棄該事件。
  1. 右邊圖的流程唯一不同就在于,如果當(dāng)前的ViewController是由層級關(guān)系的,那么當(dāng)子ViewController不能處理事件時,它會將事件繼續(xù)往上傳遞,直到傳遞到其Root ViewController,后面的流程就跟之前分析的一樣了。
  • 事件的傳遞和響應(yīng)的區(qū)別:

事件的傳遞是從上到下(父控件到子控件),事件的響應(yīng)是從下到上(順著響應(yīng)者鏈條向上傳遞:子控件到父控件。

http://www.lxweimin.com/p/f55b613b564e
http://www.cnblogs.com/zhw511006/p/3517248.html
http://blog.csdn.net/primer_programer/article/details/7009689

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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