在iOS中UIResponder類是專門用來響應用戶的操作處理各種事件的续滋,包括觸摸事件(Touch Events)、運動事件(Motion Events)鲤嫡、遠程控制事件(Remote Control Events,如插入耳機調節(jié)音量觸發(fā)的事件)粟害。我們知道UIApplication茫陆、UIView仗岖、UIViewController這幾個類是直接繼承自UIResponder,UIWindow是直接繼承自UIView的一個特殊的View,所以這些類都可以響應事件。當然我們自定義的繼承自UIView的View以及自定義的繼承自UIViewController的控制器都可以響應事件览妖。iOS里面通常將這些能響應事件的對象稱之為響應者轧拄。
下面我們根據(jù)UIResponder.h頭文件來具體介紹關于響應事件的各個方面。
一. UIResponder類
這個部分我們主要介紹3種事件類型讽膏,即觸摸事件檩电,運動事件,遠程控制事件府树。當用戶觸發(fā)某一事件時俐末,UIKit會創(chuàng)建一個UIEvent事件對象(關于iOS事件對象可以參考這篇文章),事件對象會加入到一個FIFO先進先出的隊列中,UIApplication對象處理事件時奄侠,會從隊列頭部取出一個事件對象進行分發(fā)鹅搪。
1.觸摸事件
@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;//系統(tǒng)事件干擾
這4個方法是觸摸事件的最原始處理的4個方法,分別代表觸摸屏幕遭铺,在屏幕上移動丽柿,離開屏幕以及受到系統(tǒng)優(yōu)先級別高的事件的干擾(比如來電話)取消觸摸事件。另外UIKit框架對于觸摸事件為我們提供了UIGestureRecognizer
手勢識別這個類魂挂,基本上能滿足我們的大部分需求(可以參考這篇文章)甫题。這里介紹的是最底層的處理方法,比如可以用來實現(xiàn)繪圖類型的APP.
上面提到UIApplication對象從隊列中取出事件對象進行分發(fā)涂召,對于觸摸事件來說坠非,UIApplication會首先把事件交給keyWindow,Window會將事件交給UIGestureRecognizer
處理,如果UIGestureRecognizer
識別了傳遞過來的事件果正,則交給相對應的target去處理(關于iOS手勢事件可以參考這篇文章)炎码,事件不會再傳遞,如果UIGestureRecognizer
并沒有識別傳遞過來的事件(可能是沒有視圖添加手勢秋泳,也可能手勢識別不成功)潦闲,事件會傳遞到視圖樹形結構,會分成尋找接受者和事件響應這兩個步驟迫皱。
1.在iOS視圖樹形結構中找到最終的接收者歉闰,也就是觸摸事件發(fā)生的那個最上層的View上,這一過程稱為hit-testing(測試命中),通過一層層的遍歷找到最終的命中視圖稱為hit-test view.
UIView中有兩個方法用來確定hit-test view.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
這是一張我從apple官方文檔里面的截圖卓起,這里所有的顯示的View都是加載到主window上和敬,假設我們觸摸到屏幕上ViewD的區(qū)域,當我們沒有重載UIView的
hitTest:withEvent:
和pointInside:withEvent:
這兩個方法時,系統(tǒng)默認的處理如下:
- keyWindow調用
pointInside:withEvent:
判斷觸摸點是否在其frame范圍內(nèi)戏阅,返回Yes昼弟,遍歷keyWindow的subView->ViewA. - ViewA調用
pointInside:withEvent:
判斷觸摸點是否在其frame范圍內(nèi),返回Yes奕筐,遍歷ViewA的subView->ViewB舱痘、ViewC(關于ViewB和ViewC先執(zhí)行哪個蚕键,是根據(jù)ViewA添加子控件的先后順序,總是先執(zhí)行后添加的subView.假設添加ViewB后添加ViewC) - ViewC調用
pointInside:withEvent:
判斷觸摸點是否在其frame范圍內(nèi)衰粹,返回Yes锣光,遍歷ViewC的subView->ViewD、ViewE - ViewE調用
pointInside:withEvent:
判斷觸摸點是否在其frame范圍內(nèi)铝耻,返回NO,ViewE的hitTest:withEvent:
返回nil(如果是先執(zhí)行ViewB的情況誊爹,假設ViewB還有子節(jié)點subView,由于ViewB的pointInside:withEvent:
返回NO,ViewB的hitTest:withEvent:`直接返回nil是不會再去遍歷ViewB的子節(jié)點的) - ViewD調用
pointInside:withEvent:
判斷觸摸點是否在其frame范圍內(nèi),返回Yes并且沒有子節(jié)點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
2.找到了hit-test view,下一個步驟就是響應事件泡态。說明一下搂漠,對于觸摸事件來說,無論View是否處理事件某弦,即使是application通過[application beginIgnoringInteractionEvents]
忽略了觸摸事件桐汤,上面hit-testing的過程依然存在,它只影響第二個步驟事件響應的過程靶壮。下面我們將介紹iOS響應者鏈條(Responder chain)
這是我從官方文檔里面截取的一張關于響應者鏈條的截圖怔毛。我們先看上圖左邊的情況:標注為①的地方即為步驟1找到的hit-test view 它作為第一響應者來響應這個事件,如果該view沒有通過重寫或者封裝touch系列方法來處理該事件腾降,默認touch的實現(xiàn)就是調用父類的touch方法拣度,將事件傳遞下去。在這里由1->傳遞到它的父類2螃壤,2是控制器的根view,->傳遞到vc控制器->傳遞到窗口window->傳遞到application
再看上圖右邊的情況:標注為①的地方即為步驟1找到的hit-test view,同時它是控制器的根view并且還有父視圖抗果,事件傳遞到控制器->再傳遞到父視->傳遞到控制器,再傳遞到父視圖窗口->application奸晴。其實上圖左邊部分也可以理解為窗口是控制器根視圖的父視圖冤馏。如果整個響應者鏈條結束,都沒有對事件做處理蚁滋,那么該事件會被丟棄宿接。
總結一下響應者鏈條的傳遞過程是:由第一響應者(對于觸摸事件來說是hist-test view)開始向上傳遞赘淮。如果該視圖是控制器的根視圖辕录,先傳遞給控制器,再傳遞給父視圖梢卸,如果不是控制器的根視圖走诞,直接傳遞給父視圖。
只要在響應者的處理方法里面調用父類的方法蛤高,就可以讓多個視圖和控制器響應同一個事件蚣旱,響應者鏈條的根本目的是:共享事件碑幅,讓多個視圖和控制器可以對同一事件做不同的處理。
2.運動事件
- (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事件沟涨,手機搖動觸發(fā)手機內(nèi)部的加速度傳感器,可以用來實現(xiàn)搖一搖計算運動的步數(shù)等等應用异吻。類似于觸摸事件裹赴,這3個方法分別代表事件開始、事件結束和受到系統(tǒng)干擾取消事件诀浪。
加速度計accelerometer
實際上是由三個加速度計組成棋返,分別用于測量X,Y和Z軸直線路徑速度的變化。結合所有三個加速度計可以檢測設備朝任何方向的運動和獲取設備的當前方向雷猪。對于shake事件來說睛竣,我們不關心3個方向上的運動,只作為一個事件對象來處理求摇。如果只是處理設備的大方向射沟,并不需要知道方向向量,如橫屏豎屏屏幕旋轉与境,我們可以使用UIDevice類(參考文章)躏惋。如果我們需要知道3個方向上的運動做更細致化的處理,如上了多少層樓等運動類型APP嚷辅,可以使用的核心運動框架訪問加速度計簿姨,陀螺儀和設備的運動類來做處理(Core Motion參考文章)
上面介紹的響應者鏈條對shake事件同樣適用,只不過簸搞,沒有hit-testing過程扁位,如果當前顯示的視圖界面沒有一個view聲明為第一響應者(調用becomeFirstResponder
申明并且View需要重寫canBecomeFirstResponder
方法返回YES,默認返回為NO)趁俊,默認當前視圖控制器為第一響應者域仇,并將事件沿著響應者鏈條傳遞,直到被處理寺擂。如果有視圖聲明為第一響應者暇务,就從該視圖開始傳遞事件直到被處理,如果該事件最終沒有被處理并且UIApplication的applicationSupportsShakeToEdit
屬性為YES(默認就是YES),當鍵盤顯示的時候,系統(tǒng)會有一個是否撤銷正在輸入的警告怔软。就是微信和QQ上在輸入的時候搖動手機提示撤銷輸入的那種效果垦细。關于更多撤銷方面的操作參考NSUndoManager
3.遠程控制事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event NS_AVAILABLE_IOS(4_0);
遠程控制事件一般用于多媒體事件,播放暫停上一曲下一曲快進快退等挡逼。