聲明:
這篇是根據看史上最詳細的iOSZ之事件的傳遞和響應機制-原理篇做的一個筆記褐奴。
筆記一:響應者對象(UIResponder)
UIResponder類可以接收和處理事件因為它提供了4個對象方法來處理接觸事件
UIResponder內部提供了以下方法來處理事件觸摸事件
// 一根或者多根手指開始觸摸view敦冬,系統(tǒng)會自動調用view的下面方法
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
// 一根或者多根手指在view上移動,系統(tǒng)會自動調用view的下面方法(隨著手指的移動脖旱,會持續(xù)調用該方法)
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
// 一根或者多根手指離開view,系統(tǒng)會自動調用view的下面方法
- (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;
遠程控制事件
- (void)remoteControlReceivedWithEvent:(UIEvent *)event;
總結:
UIApplication蚤氏,UIViewController踊兜,UIView都是繼承于UIResponder捏境,所以它們也可以重寫事件來接收和處理事件毁葱。
聯(lián)想:
CALayer 和 UIView的最大區(qū)別是 UIview可以處理事件 因為view是responder的子類
筆記二:UITouch對象
下面是UITouch的屬性和方法:
@interface UITouch : NSObject
NSTimeInterval timestamp; //觸摸發(fā)生的時間
UITouchPhase phase; //返回一個階段常量,指出觸摸開始倾剿、繼續(xù)前痘、結束或被取消,分別對應UITouchPhaseBegan芹缔、UITouchPhaseMoved等
NSUInteger tapCount; //輕按屏幕的次數(shù) 可以根據這個判斷單擊 雙擊 多擊
UITouchType type NS_AVAILABLE_IOS(9_0); //觸摸的類型
CGFloat majorRadius ; //觸摸的半徑
CGFloat majorRadiusTolerance;//觸摸的半徑的誤差值
//引起觸摸的視圖或Window.
UIWindow *window;
UIView *view;
NSArray <UIGestureRecognizer *> *gestureRecognizers ;//手勢數(shù)組
// 現(xiàn)在觸摸的坐標//函數(shù)返回一個CGPoint類型的值最欠,
// 表示觸摸在view這個視圖上的位置惩猫,這里返回的位置是針對view的坐標系的蚜点。
// 調用時傳入的view參數(shù)為空的話,返回的時觸摸點在整個窗口的位置禽额。
- (CGPoint)locationInView:(nullable UIView *)view;
//上一次觸摸的坐標//該方法記錄了前一個坐標值函數(shù)返回也是一個CGPoint類型的值
//表示觸摸在view這個視圖上的位置脯倒,這里返回的位置是針對view的坐標系的。
//調用時傳入的view參數(shù)為空的話藻丢,返回的時觸摸點在整個窗口的位置。
- (CGPoint)previousLocationInView:(nullable UIView *)view;
////現(xiàn)在觸摸的精確的坐標
- (CGPoint)preciseLocationInView:(nullable UIView *)view残黑;
//上一次觸摸的精確的坐標
- (CGPoint)precisePreviousLocationInView:(nullable UIView *)view
//觸摸的力度
CGFloat force;
//觸摸的最大的力度
CGFloat maximumPossibleForce;
//沿著x軸正向的方位角,當與x軸正向方向相同時,該值為0;當view參數(shù)為nil時斋否,
//默認為keyWindow返回觸針的方位角(弧度)。
- (CGFloat)azimuthAngleInView:(nullable UIView *)view NS_AVAILABLE_IOS(9_1);
//當前觸摸對象的方向上的單位向量當view參數(shù)為nil時疫诽,
//默認為keyWindow返回在觸針的方位角的方向指向的單位矢量旦委。
- (CGVector)azimuthUnitVectorInView:(nullable UIView *)view NS_AVAILABLE_IOS(9_1);
//當筆平行于平面時,該值為0
//當筆垂直于平面時,該值為Pi / 2
//觸針的高度(單位為弧度)
CGFloat altitudeAngle ;
//當每個觸摸對象的觸摸特性發(fā)生變化時,
//該值將會單獨增加,返回值是NSNumber 索引號摩钙,
//讓您關聯(lián)與原來的觸摸更新的聯(lián)系
NSNumber * _Nullable estimationUpdateIndex
//當前觸摸對象估計的觸摸特性,返回值是UITouchPropertyies一組觸摸屬性查辩,
//這些屬性將得到更新
UITouchProperties estimatedProperties
//一組期望在未來的更新報文的觸摸性能。
UITouchProperties estimatedPropertiesExpectingUpdates;
UITouch對象:
1 當前用戶一根手指觸摸屏幕市 會創(chuàng)建一個與手指相關的UITouch對象 一根手指對應著一個UITouch對象
2如果兩根手指同時觸摸一個view,那么view只會調用一個touchesBegan方法 touches參數(shù)中裝著2個UITouch對象
3如果這兩根手指一前一后分開觸摸同一個view长踊,那么view會分別調用2次touchesBegan 方法并且每次調用的touches參數(shù)只包含一個UITouchd對象
UITouch的作用
1 保存著跟手指有關的信息
2 保存手指的觸摸位置
3 當手指離開屏幕系統(tǒng)會銷毀相應的UITouch對象
筆記三 IOS的事件產生和傳遞
1 事件的觸摸事件的傳遞是從父控件傳遞到子控件 UIApplication -> Window -> 合適的View
2 應用如何找到最合適的控件來處理事件谬返?
1.首先判斷主窗口(keyWindow)自己是否能接受觸摸事件
2.判斷觸摸點是否在自己身上
3.子控件數(shù)組中從后往前遍歷子控件,重復前面的兩個步驟(所謂從后往前遍歷子控件佑刷,就是 首先查找子控件數(shù)組中最后一個元素,然后執(zhí)行1涨冀、2步驟)
4.view麦萤,比如叫做fitView,那么會把這個事件交給這個fitView壮莹,再遍歷這個fitView的子控 件,直至沒有更合適的view為止涝滴。
5.如果沒有符合條件的子控件胶台,那么就認為自己最合適處理這個事件,也就是自己是最合適的view韩脏。
3 UIView不能接收觸摸事件的三種情況:
1 不允許交互:userInteractionEnabled = NO
2 隱藏:如果把父控件隱藏铸磅,那么子控件也會隱藏说订,隱藏的控件不能接受事件
3 透明度:如果設置一個控件的透明度<0.01絮重,會直接影響子控件的透明度霎槐。alpha:0.0~0.01為透明
借用看到的圖片 覺得很清晰的表達了上面的流程:
筆記4 如何尋找最合適的view
1 首先判斷主窗口自己能否接受觸摸事件
2 觸摸點是否在自己的身上
3 從后遍歷子控件 重復前面兩個步驟
4 如果沒有符合的子控件就認為自己最合適
筆記5 尋找最合適的view的兩個方法
5.1 hitTest:withEvent:方法
1 什么時候調用丘跌?
只要事件一傳遞給一個控件,這個控件就會調用他自己的hitTest:withEvent方法
2 可以根據這個方法攔截事件
3 特殊情況:
誰都不能處理事件唁桩,窗口也不能處理。
重寫window的hitTest:withEvent:方法return nil
只能有窗口處理事件报辱。
控制器的view的hitTest:withEvent:方法return nil或者window的hitTest:withEvent:方法return self
return nil的含義:
hitTest:withEvent:中return nil的意思是調用當前hitTest:withEvent:方法的view不是合適的view单山,子控件也不是合適的view幅疼。如果同級的兄弟控件也沒有合適的view昼接,那么最合適的view就是父控件。
5.1 pointInside:withEvent:方法
pointInside:withEvent:方法判斷點在不在當前view上(方法調用者的坐標系上)如果返回YES,代表點在方法調用者的坐標系上;返回NO代表點不在方法調用者的坐標系上漂辐,那么方法調用者也就不能處理事件。
筆記6 事件的響應
1事件的響應
1用戶點擊屏幕后產生一個觸摸事件經過一系列的傳遞 會找到最合適的視圖 也就是筆記4和5 為事件的傳遞過程
2 找到合適的視圖后 會通過touch方法將事件順著響應鏈條向上傳遞窘俺,將事件交給上一個響應者處理
2 響應鏈條
響應者鏈條:在iOS程序中無論是最后面的UIWindow還是最前面的某個按鈕复凳,它們的擺放是有前后關系的,一個控件可以放到另一個控件上面或下面对途,那么用戶點擊某個控件時是觸發(fā)上面的控件還是下面的控件呢髓棋,這種先后關系構成一個鏈條就叫“響應者鏈”。也可以說膳犹,響應者鏈是由多個響應者對象連接起來的鏈條签则。在iOS中響應者鏈的關系可以用下圖表示:
3 響應者對象:能處理事件的對象,也就是繼承自UIResponder的對象
4如何判斷上一個響應者
1> 如果當前這個view是控制器的view,那么控制器就是上一個響應者
2> 如果當前這個view不是控制器的view,那么父控件就是上一個響應者
5 響應鏈的事件傳遞過程
1 如果當前view是控制器的view 那么控制器就是上一個響應者 事件就傳遞給控制器豺旬。如果當前view不是控制器的view 那么父視圖就是當前view的上一個響應者柒凉,事件就會傳遞給它的父視圖
2 在視圖層次結構的最頂級視圖,如果也不能處理的事件或消息 則將其事件或消息傳遞給window對象進行處理
3 如果window對象也不處理 則其講事件或消息傳遞給UIApplication對象
4 如果UIApplication也不能處理就將改事件或消息給丟棄
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
// 默認會把事件傳遞給上一個響應者,上一個響應者是父控件,交給父控件處理
[super touchesBegan:touches withEvent:event];
// 注意不是調用父控件的touches方法坦刀,而是調用父類的touches方法
// super是父類 superview是父控件
}
6事件的傳遞與響應:
1、當一個事件發(fā)生后央渣,事件會從父控件傳給子控件渴频,也就是說由UIApplication -> UIWindow -> UIView -> initial view,以上就是事件的傳遞,也就是尋找最合適的view的過程卜朗。
2场钉、接下來是事件的響應。首先看initial view能否處理這個事件逛万,如果不能則會將事件傳遞給其上級視圖(inital view的superView);如果上級視圖仍然無法處理則會繼續(xù)往上傳遞得封;一直傳遞到視圖控制器view controller指郁,首先判斷視圖控制器的根視圖view是否能處理此事件;如果不能則接著判斷該視圖控制器能否處理此事件疫粥,如果還是不能則繼續(xù)向上傳 遞腰懂;(對于第二個圖視圖控制器本身還在另一個視圖控制器中,則繼續(xù)交給父視圖控制器的根視圖绣溜,如果根視圖不能處理則交給父視圖控制器處理)涮毫;一直到 window贷屎,如果window還是不能處理此事件則繼續(xù)交給application處理,如果最后application還是不能處理此事件則將其丟棄
3唉侄、在事件的響應中,如果某個控件實現(xiàn)了touches...方法恬叹,則這個事件將由該控件來接受,如果調用了[supertouches….];就會將事件順著響應者鏈條往上傳遞唯鸭,傳遞給上一個響應者硅确;接著就會調用上一個響應者的touches….方法
總結:
事件的傳遞是從上到下(父控件到子控件),事件的響應是從下到上(順著響應者鏈條向上傳遞:子控件到父控件菱农。