iOS控件之UIResponder類html
這個部分咱們主要介紹3種事件類型,即觸摸事件,運動事件,遠程控制事件。當用戶觸發某一事件時,UIKit會建立一個UIEvent事件對象(關於iOS事件對象能夠參考這篇文章),事件對象會加入到一個FIFO先進先出的隊列中,UIApplication對象處理事件時,會從隊列頭部取出一個事件對象進行分發。微信
@interface UIResponder : NSObject - (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;//系統事件干擾
這4個方法是觸摸事件的最原始處理的4個方法,分別表明觸摸屏幕,在屏幕上移動,離開屏幕以及受到系統優先級別高的事件的干擾(好比來電話)取消觸摸事件。另外UIKit框架對於觸摸事件爲咱們提供了UIGestureRecognizer
手勢識別這個類,基本上能知足咱們的大部分需求(能夠參考這篇文章)。這裏介紹的是最底層的處理方法,好比能夠用來實現繪圖類型的APP.
上面提到UIApplication對象從隊列中取出事件對象進行分發,對於觸摸事件來講,UIApplication會首先把事件交給keyWindow,Window會將事件交給UIGestureRecognizer
處理,若是UIGestureRecognizer
識別了傳遞過來的事件,則交給相對應的target去處理(關於iOS手勢事件能夠參考這篇文章),事件不會再傳遞,若是UIGestureRecognizer
並無識別傳遞過來的事件(多是沒有視圖添加手勢,也可能手勢識別不成功),事件會傳遞到視圖樹形結構,會分紅尋找接受者和事件響應這兩個步驟。
1.在iOS視圖樹形結構中找到最終的接收者,也就是觸摸事件發生的那個最上層的View上,這一過程稱爲hit-testing(測試命中),經過一層層的遍歷找到最終的命中視圖稱爲hit-test view.
UIView中有兩個方法用來肯定hit-test view.app
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event; - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
這是一張我從apple官方文檔裏面的截圖,這裏全部的顯示的View都是加載到主window上,假設咱們觸摸到屏幕上ViewD的區域,當咱們沒有重載UIView的hitTest:withEvent:
和pointInside:withEvent:
這兩個方法時,系統默認的處理以下:框架
pointInside:withEvent:
判斷觸摸點是否在其frame範圍內,返回Yes,遍歷keyWindow的subView->ViewA.pointInside:withEvent:
判斷觸摸點是否在其frame範圍內,返回Yes,遍歷ViewA的subView->ViewB、ViewC(關於ViewB和ViewC先執行哪一個,是根據ViewA添加子控件的前後順序,老是先執行後添加的subView.假設添加ViewB後添加ViewC)pointInside:withEvent:
判斷觸摸點是否在其frame範圍內,返回Yes,遍歷ViewC的subView->ViewD、ViewEpointInside:withEvent:
判斷觸摸點是否在其frame範圍內,返回NO,ViewE的hitTest:withEvent:
返回nil(若是是先執行ViewB的狀況,假設ViewB還有子節點subView,因爲ViewB的pointInside:withEvent:
返回NO,ViewB的hitTest:withEvent:`直接返回nil是不會再去遍歷ViewB的子節點的)pointInside:withEvent:
判斷觸摸點是否在其frame範圍內,返回Yes而且沒有子節點subView,ViewD的hitTest:withEvent:
返回ViewD自己,即爲最終的hit-test view(不會再遍歷ViewB)iewB
須要注意的是:View.isHidden=YES View.alpha<=0.01 View.userInterfaceEnable=NO View.enable = NO(指繼承自UIControl的View)的這4種狀況下,View的pointInside返回NO,hitTest方法返回nil
默認UIImageView
的userInterfaceEnable=NO
ide
2.找到了hit-test view,下一個步驟就是響應事件。說明一下,對於觸摸事件來講,不管View是否處理事件,即便是application經過[application beginIgnoringInteractionEvents]
忽略了觸摸事件,上面hit-testing的過程依然存在,它隻影響第二個步驟事件響應的過程。下面咱們將介紹iOS響應者鏈條(Responder chain)測試
再看上圖右邊的狀況:標註爲①的地方即爲步驟1找到的hit-test view,同時它是控制器的根view而且還有父視圖,事件傳遞到控制器->再傳遞到父視->傳遞到控制器,再傳遞到父視圖窗口->application。其實上圖左邊部分也能夠理解爲窗口是控制器根視圖的父視圖。若是整個響應者鏈條結束,都沒有對事件作處理,那麼該事件會被丟棄。ui
總結一下響應者鏈條的傳遞過程是:由第一響應者(對於觸摸事件來講是hist-test view)開始向上傳遞。若是該視圖是控制器的根視圖,先傳遞給控制器,再傳遞給父視圖,若是不是控制器的根視圖,直接傳遞給父視圖。
只要在響應者的處理方法裏面調用父類的方法,就可讓多個視圖和控制器響應同一個事件,響應者鏈條的根本目的是:共享事件,讓多個視圖和控制器能夠對同一事件作不一樣的處理。spa
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event NS_AVAILABLE_IOS(3_0); - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event NS_AVAILABLE_IOS(3_0); - (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event NS_AVAILABLE_IOS(3_0);
這3個方法是運動事件的最原始處理的3個方法,這裏處理的運動事件特指shake事件,手機搖動觸發手機內部的加速度傳感器,能夠用來實現搖一搖計算運動的步數等等應用。相似於觸摸事件,這3個方法分別表明事件開始、事件結束和受到系統干擾取消事件。
加速度計accelerometer
其實是由三個加速度計組成,分別用於測量X,Y和Z軸直線路徑速度的變化。結合全部三個加速度計能夠檢測設備朝任何方向的運動和獲取設備的當前方向。對於shake事件來講,咱們不關心3個方向上的運動,只做爲一個事件對象來處理。若是隻是處理設備的大方向,並不須要知道方向向量,如橫屏豎屏屏幕旋轉,咱們可使用UIDevice類(參考文章)。若是咱們須要知道3個方向上的運動作更細緻化的處理,如上了多少層樓等運動類型APP,可使用的核心運動框架訪問加速度計,陀螺儀和設備的運動類來作處理(Core Motion參考文章)
上面介紹的響應者鏈條對shake事件一樣適用,只不過,沒有hit-testing過程,若是當前顯示的視圖界面沒有一個view聲明爲第一響應者(調用becomeFirstResponder
申明而且View須要重寫canBecomeFirstResponder
方法返回YES,默認返回爲NO),默認當前視圖控制器爲第一響應者,並將事件沿着響應者鏈條傳遞,直到被處理。若是有視圖聲明爲第一響應者,就從該視圖開始傳遞事件直到被處理,若是該事件最終沒有被處理而且UIApplication的applicationSupportsShakeToEdit
屬性爲YES(默認就是YES),當鍵盤顯示的時候,系統會有一個是否撤銷正在輸入的警告。就是微信和QQ上在輸入的時候搖動手機提示撤銷輸入的那種效果。關於更多撤銷方面的操做參考NSUndoManager3d
注意:UIView.enable = NO;時也不能響應事件!代理