iOS開發之觸摸事件

如果這篇文章幫助到了您,希望您能點擊一下喜歡或者評論,你們的支持是我前進的強大動力.謝謝!

觸摸事件(Multitouch events)

  • 手指觸摸了(或者手機加速事件,遠程遙控事件等...這里先只介紹觸摸事件)某個響應者對象時,會調用調用響應者對象的一些方法
  • 響應者對象都能夠接收并處理事件
  • 在iOS中不是任何對象都能處理事件,只有繼承了UIResponder的對象才能接收并處理事件。我們稱之為“響應者對象”
  • UIApplication、UIViewController、UIView都繼承自UIResponder,因此它們都是響應者對象,都能夠接收并處理事件

繼承了UIResponder如何處理事件

因為UIResponder內部提供了以下方法來處理事件

  • 比如觸摸事件會調用以下方法:
 - (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;
  • 遠程控制事件會調用:(下面暫時不介紹)
 - (void)remoteControlReceivedWithEvent:(UIEvent *)event;

如何監聽UIView的觸摸事件?

  • 想要監聽UIView的觸摸事件,首先第一步要自定義UIView,

  • 因為只有實現了UIResponder的事件方法才能夠監聽事件.

  • UIView的觸摸事件主要有:

一根或者多根手指開始觸摸view,系統會自動調用view的下面方法.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
  
一根或者多根手指在view上移動時,系統會自動調用view的下面方法
(隨著手指的移動,會持續調用該方法,也就是說這個方法會調用很多次)
 - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
   
一根或者多根手指離開view,系統會自動調用view的下面方法
 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
  • 參數touches介紹
    • 是一個NSSet集合,里面裝的是UITouch對象
    • 什么是UITouch對象?
      當用戶用一根手指觸摸屏幕時,會創建一個與手指相關聯的UITouch對象
      一根手指對應一個UITouch對象
      UITouch的作用:保存著跟手指相關的信息,比如觸摸的位置、時間、階段
      當手指移動時,系統會更新同一個UITouch對象,使之能夠一直保存該手指在的觸摸位置
      當手指離開屏幕時,系統會銷毀相應的UITouch對象
    • UITouch屬性
     觸摸產生時所處的窗口
     @property(nonatomic,readonly,retain) UIWindow    *window;
    
     觸摸產生時所處的視圖
     @property(nonatomic,readonly,retain) UIView      *view;
    
     短時間內點按屏幕的次數,可以根據tapCount判斷單擊、雙擊或更多的點擊
     @property(nonatomic,readonly) NSUInteger          tapCount;
    
     記錄了觸摸事件產生或變化時的時間,單位是秒
     @property(nonatomic,readonly) NSTimeInterval      timestamp;
    
     當前觸摸事件所處的狀態
     @property(nonatomic,readonly) UITouchPhase        phase;
    
    • UITouch的方法
     - (CGPoint)locationInView:(UIView *)view;
    

返回值表示觸摸在view上的位置
這里返回的位置是針對view的坐標系的(以view的左上角為原點(0, 0))
調用時傳入的view參數為nil的話,返回的是觸摸點在UIWindow的位置

  • (CGPoint)previousLocationInView:(UIView *)view;
    該方法記錄了前一個觸摸點的位置

- event參數介紹
  - 每產生一個事件,就會產生一個UIEvent對象
  - UIEvent:稱為事件對象,記錄事件產生的時刻和類型
  - 常見屬性
```objc
   事件類型
   @property(nonatomic,readonly) UIEventType     type;
   @property(nonatomic,readonly) UIEventSubtype  subtype;

    事件產生的時間
   @property(nonatomic,readonly) NSTimeInterval  timestamp;

   UIEvent還提供了相應的方法可以獲得在某個view上面的觸摸對象(UITouch)

事件的產生與傳遞

  • 當發生一個觸摸事件后,系統會將該事件加入到一個由UIApplication管理的事件隊列中.
    UIApplication會從事件隊列中取出最前面的事件,交給主窗口.
    主窗口會在視圖層次結構中找到一個'最合適的視圖'來處理觸摸事件
    觸摸事件的傳遞是從父控件傳遞到子控件的.
    如果一個父控件不能接收事件,那么它里面的了子控件也不能夠接收事件.

    • 一個控件不能夠接收事件情況下
      1.不接收用戶交互時不能夠處理事件
      userInteractionEnabled = NO
      2.當一個控件隱藏的時候不能夠接收事件
      Hidden = YES的時候
      3.當一個控件為透明的時候也不能夠接收事件 (alpha <= 0.01)
      注意:UIImageView的userInteractionEnabled默認就是NO,
      因此UIImageView以及它的子控件默認是不能接收觸摸事件的
  • 如何尋找最合適的View(重點)
    1.先判斷自己是否能夠接收觸摸事件,如果能再繼續往下判斷,
    2.再判斷觸摸的當前點在不在自己的身上.
    3.如果在自己身上,它會從后往前遍歷子控件,遍歷出每一個子控件后,重復前面的兩個步驟.
    4.如果沒有符合條件的子控件,那么它自己就是最適合的View.

  • 觸摸事件處理的詳細過程
    用戶點擊屏幕后產生的一個觸摸事件,經過一系列的傳遞過程后,會找到最合適的視圖控件來處理這個事件

    找到最合適的視圖控件后,就會調用控件的touches方法來作具體的事件處理
    touchesBegan…
    touchesMoved…
    touchedEnded…
    
    touchesBegan方法的默認做法是將事件'順著響應者鏈條'向上傳遞,將事件交給上一個響應者進行處理
    
  • 什么是響應者鏈條

    • 通俗的來說就是由多個響應者對象連接起來的鏈條.
  • 所以事件傳遞與響應的完整過程就是:
    1.在產生一個事件時,系統會將該事件加入到一個由UIApplication管理的事件隊列中,
    2.UIApplication會從事件隊列中取出最前面的事件,將它傳遞給先發送事件給應用程序的主窗口.
    3.主窗口會調用hitTest方法尋找最適合的視圖控件,找到后就會調用視圖控件的touches方法來做具體的事情.
    4.當調用touches方法,它的默認做法, 就會將事件順著響應者鏈條往上傳遞,
    5.傳遞給上一個響應者,接著就會調用上一個響應者的touches方

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

hitTest與pointInside方法

  • hitTest方法
    作用:尋找最適合的View
    參數:當前手指所在的點.產生的事件
    返回值:返回誰, 誰就是最適合的View.
    什么時候用調用:只要一個事件,傳遞給一個控件時, 就會調用這個控件的hitTest方法
    -(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
    • hitTest方法底層實現
作用:當一個事件傳遞給當前View的時候就會調用這個方法.
第一個參數:當前手指在屏幕上的點
第二個參數:當前產生的事件
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
   
   1.查看自己能不能接收事件 
    if(self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01){
        return nil; 
    }
   2.判斷當前的點在不在自己身上. 
    if(![self pointInside:point withEvent:event]){
        return nil;
    } 
   
   查看自己是不是最適合的view 
   從后往前遍歷自己的子控制器. 
    int count = (int)self.subviews.count; 
    for (int i = count - 1; i >=0; i--) { 
       取出子控制器. 
        UIView *childView = self.subviews[i]; 
       要把當前的點轉換成子控件上的坐標點. 
        CGPoint childP =  [self convertPoint:point toView:childView]; 
        UIView *view = [childView hitTest:childP withEvent:event];
        如果有值,直接返回,返回的就是最適合的View.
        if (view) {
            return view;
        } 
    }
    沒有找到比自己更適合的View.自己就是最適合的View 
    return self; 
}
  • PointInside方法
    作用:判斷point在不在方法調用者上-------先要轉換坐標系[self convertPoint:point toView:childView];
    point:必須是方法調用者的坐標系
    什么時候調用:hitTest方法底層會調用這個方法,判斷點在不在控件上.
    -(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event{
    return YES;
    }

To有錯誤和意見請大家指出,謝謝大家,一起進步!

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

推薦閱讀更多精彩內容