iOS事件的響應和傳遞機制


跟二狗子哥哥交流的時候,他總說我,說的過程太業余。故 好好學習整理一下。努力不那么業余。

一、事件的產生、傳遞、響應:

1、事件從父控件依次傳遞到子控件,尋找最合適的子控件View。

2、尋找最合適的View的底層實現,攔截事件的處理。

3、找到最合適的view之后的事件處理,也就是事件響應,重寫touch方法等。

 

傳遞過程中比較重要的兩點:

1.如何尋找最合適的view
2.尋找最合適的view的底層實現(hitTest:withEvent:底層實現)

觸摸事件 加速事件 遠程控制事件 我們現在 討論觸摸事件

 

二、響應者對象(UIResponder)

只有繼承了UIResponder的對象,才可以接受並處理事件,我們稱之為響應者對象

響應者對象有:UIApplication UIViewController UIView

UIResponder提供了方法以下方法處理觸摸事件:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;

- (void)touchesEstimatedPropertiesUpdated:(NSSet<UITouch *> *)touches NS_AVAILABLE_IOS(9_1);

 

Presses事件

- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);

- (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);

- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);

- (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0);

 

UIResponder提供了方法以下方法處理加速事件:

- (void)motionBegan:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);

- (void)motionEnded:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);

- (void)motionCancelled:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);

 

UIResponder提供了方法以下方法處理遠程事件:

- (void)remoteControlReceivedWithEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(4_0);

 

另外還有:

- (BOOL)canPerformAction:(SEL)action withSender:(nullable id)sender NS_AVAILABLE_IOS(3_0);

 

允許一個事件處理是可轉發給其他對象, 默認情況是 調用方法 -canPerformAction:withSender: 來返回自己或者提交返回給響應鏈去判斷處理。

// Allows an action to be forwarded to another target. By default checks -canPerformAction:withSender: to either return self, or go up the responder chain.

- (nullable id)targetForAction:(SEL)action withSender:(nullable id)sender NS_AVAILABLE_IOS(7_0)

 

三、事件傳遞:用戶的觸摸事件,首先被封裝成一個UIEvent事件,然后UIEvent事件傳遞給UIResponder的事件,進行判斷,尋找最合適的View。

UIEvent事件中會有UITouch對象集,保存着UITouch對象

@property(nonatomic, readonly, nullable) NSSet <UITouch *> *allTouches;

一根手指對應一個UITouch對象,

如果兩根手指同事觸摸,會調用一次- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;,其中的UIEvent事件中有兩個UITouch對象

如果兩根手指先后觸摸,會調用兩次- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event;方法,每個方法的UIEvent事件中各有一個UITouch對象

UITouch中保存着手指相關的信息,觸摸的位置,時間,階段。

當手指移動時,系統會更新同一個UITouch對象,使之能夠一直保存該手指在的觸摸位置

當手指離開屏幕時,系統會銷毀相應的UITouch對象

事件產生之后,系統會將事件添加到UIApplication管理的事件隊列中,UIApplication會從事件隊列中取出最前面的事件,並將事件分發下去處理,先將事件發送給應用程序的主窗口 keyWindow。

keyWindow會在視圖層次結構中找到一個最合適的視圖來處理觸摸事件,這也是整個事件處理過程的第一步。

事件產生之后,就是事件的傳遞過程:

從父控件到子控件尋找合適的視圖控件:

         - (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;

          recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system

         遞歸調用-pointInside:withEvent:方法,判斷point是否在其范圍內

 

  • 1.首先判斷主窗口(keyWindow)自己是否能接受觸摸事件

 

 

  • 2.判斷觸摸點是否在自己身上(pointInside方法)

       - (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;

         // default returns YES if point is in bounds  如果點在bouns范圍內,默認返回yes

  • 3.子控件數組中從后往前遍歷子控件,重復前面的兩個步驟(所謂從后往前遍歷子控件,就是首先查找子控件數組中最后一個元素,然后執行1、2步驟)
  • 4.view,比如叫做fitView,那么會把這個事件交給這個fitView,再遍歷這個fitView的子控件,直至沒有更合適的view為止。
  • 5.如果沒有符合條件的子控件,那么就認為自己最合適處理這個事件,也就是自己是最合適的view。

期間用到的重要的兩個方法:

- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;

 - (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event

UIView不能接收觸摸事件的三種情況:

  • 不允許交互:userInteractionEnabled = NO
  • 隱藏:如果把父控件隱藏,那么子控件也會隱藏,隱藏的控件不能接受事件
  • 透明度:如果設置一個控件的透明度<0.01,會直接影響子控件的透明度。alpha:0.0~0.01為透明。

主要方法解析:

- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;

調用時機:

只要事件一傳給某個控件,控件就會調用自己的該方法,尋找合適的子控件返回。

作用:

尋找合適的子控件返回

注 意:不管這個控件能不能處理事件,也不管觸摸點在不在這個控件上,事件都會先傳遞給這個控件,隨后再調用hitTest:withEvent:方法

 - (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event

攔截事件的處理:

  • 正因為hitTest:withEvent:方法可以返回最合適的view,所以可以通過重寫hitTest:withEvent:方法,返回指定的view作為最合適的view。
  • 不管點擊哪里,最合適的view都是hitTest:withEvent:方法中返回的那個view。
  • 通過重寫hitTest:withEvent:,就可以攔截事件的傳遞過程,想讓誰處理事件誰就處理事件。

如果hitTest:withEvent:方法中返回nil,那么調用該方法的控件本身和其子控件都不是最合適的view,也就是在自己身上沒有找到更合適的view。那么最合適的view就是該控件的父控件。

事件傳遞順序:

產生觸摸事件->UIApplication事件隊列->[UIWindow hitTest:withEvent:]->返回更合適的view->[子控件 hitTest:withEvent:]->返回最合適的view

重寫的技巧:在父控件的hitTest:withEvent:中返回子控件作為最合適的view!

 

hit:withEvent:方法底層會調用pointInside:withEvent:方法判斷點在不在方法調用者的坐標系上

 

 

pointInside:withEvent:方法

判斷點在不在當前view上(方法調用者的坐標系上)如果返回YES,代表點在方法調用者的坐標系上;返回NO代表點不在方法調用者的坐標系上,那么方法調用者也就不能處理事件。

 

四、事件的響應:

響應者鏈條

如何判斷上一個響應者

  • 1> 如果當前這個view是控制器的view,那么控制器就是上一個響應者
  • 2> 如果當前這個view不是控制器的view,那么父控件就是上一個響應者

響應者鏈的事件傳遞過程:

  • 1>如果當前view是控制器的view,那么控制器就是上一個響應者,事件就傳遞給控制器;如果當前view不是控制器的view,那么父視圖就是當前view的上一個響應者,事件就傳遞給它的父視圖
  • 2>在視圖層次結構的最頂級視圖,如果也不能處理收到的事件或消息,則其將事件或消息傳遞給window對象進行處理
  • 3>如果window對象也不處理,則其將事件或消息傳遞給UIApplication對象
  • 4>如果UIApplication也不能處理該事件或消息,則將其丟棄

五、事件處理的整個流程總結:
  1.觸摸屏幕產生觸摸事件后,觸摸事件會被添加到由UIApplication管理的事件隊列中(即,首先接收到事件的是UIApplication)。
  2.UIApplication會從事件隊列中取出最前面的事件,把事件傳遞給應用程序的主窗口(keyWindow)。
  3.主窗口會在視圖層次結構中找到一個最合適的視圖來處理觸摸事件。(至此,第一步已完成)
  4.最合適的view會調用自己的touches方法處理事件
  5.touches默認做法是把事件順着響應者鏈條向上拋。

參考:http://www.cnblogs.com/machao/p/5471094.html

十分感謝前輩的分享


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com