No5 觸摸事件

一 Modal

  • 除了push之外,還有另外一種控制器的切換方式,那就是Modal
  • 任何控制器都能通過Modal的形式展示出來
  • Modal的默認效果:新控制器從屏幕的最底部往上鑽,直到蓋住之前的控制器爲止

  • 以Modal的形式展示控制器

- (void)presentViewController:(UIViewController *)viewControllerToPresent animated: (BOOL)flag completion:(void (^)(void))completion
  • 關閉當初Modal出來的控制器
- (void)dismissViewControllerAnimated: (BOOL)flag completion: (void (^)(void))completion;

modal:會把新控制器的view添加窗口上,但是不會修改窗口的根控制器
modal:會把新控制器強引用,誰modal,誰就強引用,爲什麼要強引用,如果不強引用,新創建的控制器就會被銷燬,就不能處理modal出來界面的業務邏輯.

  • modal原理解析
 // 從下往上鑽的動畫
 // 首先讓oneVc的view顯示在窗口的底部
    oneVc.view.transform = CGAffineTransformMakeTranslation(0, keyWindow.bounds.size.height);   
 // 動畫,往上移動,還原形變
    [UIView animateWithDuration:0.5 animations:^{
//   還原形變
//  CGAffineTransformIdentity清空所有的形變,所有的形變參數都是0
       oneVc.view.transform = CGAffineTransformIdentity;
      }];
// transform:可以用來做控件的形變,平移,縮放,旋轉

二 iOS中的事件

  • 事件分3大類型
    • 觸摸事件 加速計事件 遠程控制事件
  • 響應者對象
    • 在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是UIResponder的子類,可以覆蓋下列4個方法處理不同的觸摸事件
//  一根或者多根手指開始觸摸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

//  觸摸結束前,某個系統事件(例如電話呼入)會打斷觸摸過程,系統會自動調用view的下面方法
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
// 提示:touches中存放的都是UITouch對象
  • UITouch
    • 當用戶用一根手指觸摸屏幕時,會創建一個與手指相關聯的UITouch對象
    • 一根手指對應一個UITouch對象
    • UITouch的作用
      • 保存着跟手指相關的信息,比如觸摸的位置、時間、階段
    • 當手指移動時,系統會更新同一個UITouch對象,使之能夠一直保存該手指在的觸摸位置
    • 當手指離開屏幕時,系統會銷燬相應的UITouch對象

提示:iPhone開發中,要避免使用雙擊事件!

  • 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;
// 該方法記錄了前一個觸摸點的位置
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    // 想讓控件隨着手指移動而移動,監聽手指移動
    // 獲取UITouch對象
    UITouch *touch = [touches anyObject];

    // 獲取當前點的位置
    CGPoint curP = [touch locationInView:self];

    // 獲取上一個點的位置
    CGPoint preP = [touch previousLocationInView:self];

    // 獲取它們x軸的偏移量,每次都是相對上一次
    CGFloat offsetX = curP.x - preP.x;

    // 獲取y軸的偏移量
    CGFloat offsetY = curP.y - preP.y;

    // 修改控件的形變或者frame,center,就可以控制控件的位置
    // 形變也是相對上一次形變(平移)
    // CGAffineTransformMakeTranslation:會把之前形變給清空,重新開始設置形變參數
    // make:相對於最原始的位置形變
    // CGAffineTransform t:相對這個t的形變的基礎上再去形變
    // 如果相對哪個形變再次形變,就傳入它的形變
    self.transform = CGAffineTransformTranslate(self.transform, offsetX, offsetY);
}

三 事件的產生和傳遞

  • UIEvent

    • 每產生一個事件,就會產生一個UIEvent對象
    • UIEvent:稱爲事件對象,記錄事件產生的時刻和類型
    • 常見屬性
      //  事件類型
      @property(nonatomic,readonly) UIEventType     type;
      @property(nonatomic,readonly) UIEventSubtype  subtype;
      //  事件產生的時間
      @property(nonatomic,readonly) NSTimeInterval  timestamp;
      
  • 一次完整的觸摸過程,會經歷3個狀態:
觸摸開始:- (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
  • 事件的產生和傳遞
    • 發生觸摸事件後,系統會將該事件加入到一個由UIApplication管理的事件隊列中
    • UIApplication會從事件隊列中取出最前面的事件,並將事件分發下去以便處理,通常,先發送事件給應用程序的主窗口(keyWindow)
    • 主窗口會在視圖層次結構中找到一個最合適的視圖來處理觸摸事件,這也是整個事件處理過程的第一步
    • 找到合適的視圖控件後,就會調用視圖控件的touches方法來作具體的事件處理
      • touchesBegan…
      • touchesMoved…
      • touchedEnded…
  • 實例:
    這裏寫圖片描述

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

    • 不接收用戶交互:userInteractionEnabled = NO
    • 隱藏 hidden = YES
    • 透明 alpha = 0.0 ~ 0.01

提示:UIImageView的userInteractionEnabled默認就是NO,因此UIImageView以及它的子控件默認是不能接收觸摸事件的

四 hitTest 方法

  • hitTest
    • 產生觸摸事件 -> UIApplication事件隊列 -> [UIWindow hitTest] 去尋找最合適的view
// 什麼時候調用:只要事件一傳遞給一個控件就會調用
// 作用:尋找最合適的view給你
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
   UIView *fitView = [super hitTest:point withEvent:event];
   return fitView;   
}

使用return nil,可以攔截事件傳遞過程,想讓誰處理事件誰處理事件

  • 方法底層實現代碼如下:
// UIApplication -> [UIWindow hitTest:withEvent:]尋找最合適的view告訴系統
// point:當前手指觸摸的點
// point:是方法調用者座標系上的點
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
//   UIView *fitView = [super hitTest:point withEvent:event];
    // 1.判斷下窗口能否接收事件
    if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;

    // 2.判斷下點在不在窗口上
    // 不在窗口上
    if ([self pointInside:point withEvent:event] == NO) return nil;

    // 3.從後往前遍歷子控件數組
    int count = (int)self.subviews.count;
    // 0 1
    for (int i = count - 1; i >= 0; i--) {
        // 獲取子控件
        UIView *childView = self.subviews[i];

        // 座標系的轉換,把窗口上的點轉換爲子控件上的點
        // 把自己控件上的點轉換成哪個控件上點
        CGPoint childP = [self convertPoint:point toView:childView];

         UIView *fitView = [childView hitTest:childP withEvent:event];

        if (fitView) {// 如果能找到最合適的view
            return fitView;
        }
    } 
    // 4.沒有比自己更合適的view
    return self;
}
  • 判斷傳入過來的點在不在方法調用者的座標系上
// point:是方法調用者座標系上的點
 - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
 {
    return NO;
 }

五 響應者鏈條

  • 觸摸事件處理的詳細過程
    • 用戶點擊屏幕後產生一個觸摸事件,經過一系列傳遞過程後,會找到最合適的視圖控件來處理這個事件
    • 找到最合適的視圖控件後,就會調用控件的touches方法來作事件處理
    • touches方法的默認做法是將事件順着響應者鏈條向上傳遞,將事件交給上一個響應者進行處理
  • 響應者鏈條:是由多個響應者對象連接起來的鏈條
    • 作用:能很清楚的看見每個響應者之間的聯繫,並且可以讓一個事件多個對象處理。
  • 事件傳遞的完整過程
    • 1> 先將事件對象由上往下傳遞(由父控件傳遞給子控件),找到最合適的控件來處理這個事件。
    • 2> 調用最合適控件的touches….方法
    • 3> 如果調用了[super touches….];就會將事件順着響應者鏈條往上傳遞,傳遞給上一個響應者
    • 4> 接着就會調用上一個響應者的touches….方法
  • 如何判斷上一個響應者

    • 1> 如果當前這個view是控制器的view,那麼控制器就是上一個響應者
    • 2> 如果當前這個view不是控制器的view,那麼父控件就是上一個響應者
  • 響應者鏈的事件傳遞過程

    • 1.如果view的控制器存在,就傳遞給控制器;如果控制器不存在,則將其傳遞給它的父視圖
    • 2.在視圖層次結構的最頂級視圖,如果也不能處理收到的事件或消息,則其將事件或消息傳遞給window對象進行處理
    • 3.如果window對象也不處理,則其將事件或消息傳遞給UIApplication對象
    • 4.如果UIApplication也不能處理該事件或消息,則將其丟棄

六 手勢識別

  • 通過touches方法監聽view觸摸事件,有很明顯的幾個缺點
    • 必須得自定義view
    • 由於是在view內部的touches方法中監聽觸摸事件,因此默認情況下,無法讓其他外界對象監聽view的觸摸事件
    • 不容易區分用戶的具體手勢行爲
  • 手勢識別器—-UIGestureRecognizer
    • UIGestureRecognizer是一個抽象類,定義了所有手勢的基本行爲,使用它的子類才能處理具體的手勢
 UITapGestureRecognizer(敲擊)
 UIPinchGestureRecognizer(捏合,用於縮放)
 UIPanGestureRecognizer(拖拽)
 UISwipeGestureRecognizer(輕掃)
 UIRotationGestureRecognizer(旋轉)
 UILongPressGestureRecognizer(長按)
  • 輕掃手勢
- (void)setUpSwipe
{
    // 輕掃
    // 一個手勢只能對應一個方向
    // 默認輕掃的方向往左
    UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipe:)];

    // 設置輕掃的方向
    swipe.direction = UISwipeGestureRecognizerDirectionLeft;

    [_imageView addGestureRecognizer:swipe];

    // 默認輕掃的方向往右
    UISwipeGestureRecognizer *swipeR = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipe:)];

    // 設置輕掃的方向
    swipeR.direction = UISwipeGestureRecognizerDirectionRight;

    [_imageView addGestureRecognizer:swipeR];
}


- (void)swipe:(UISwipeGestureRecognizer *)swipe
{
    if (swipe.direction == UISwipeGestureRecognizerDirectionRight) {
        // 往右邊輕掃   
    }else{ 
        // 往左邊輕掃
        NSLog(@"%s左邊",__func__);
    }
}
  • 添加長按手勢
- (void)setUpLongPress
{
    // 長按手勢
    UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];

    [_imageView addGestureRecognizer:longPress];
}

// 什麼時候調用:長按的時候調用,而且只要手指不離開,拖動的時候會一直調用,手指擡起的時候也會調用
- (void)longPress:(UILongPressGestureRecognizer *)longPress
{
    // 注意:在以後開發中,長按手勢一般需要做判斷
    if (longPress.state == UIGestureRecognizerStateEnded) {   
        NSLog(@"%s",__func__);
    }
}
  • 添加點按手勢,需要添加代理tap.delegate = self;
  • 添加旋轉手勢
- (void)setUpRotation
{
    // 旋轉
    UIRotationGestureRecognizer *rotation = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(rotation:)];

    rotation.delegate = self;

    [_imageView addGestureRecognizer:rotation];
}
- (void)rotation:(UIRotationGestureRecognizer *)rotation
{
    // 獲取的角度是相對於最原始的角度
    NSLog(@"%f",rotation.rotation);
    // 旋轉圖片
    _imageView.transform = CGAffineTransformRotate(_imageView.transform, rotation.rotation);
    // 復位,只要想相對於上一次旋轉就復位
    rotation.rotation = 0;   
}
  • 縮放手勢
- (void)setUpPinch
{
    // 捏合
    UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinch:)];

    pinch.delegate = self;

    [_imageView addGestureRecognizer:pinch];
}

- (void)pinch:(UIPinchGestureRecognizer *)pinch
{ 
    _imageView.transform = CGAffineTransformScale(_imageView.transform, pinch.scale, pinch.scale);
    // 復位
    pinch.scale = 1;
}
  • 如果同時添加縮放和旋轉手勢,代理方法
// Simultaneously:同時
// 是否同時支持多個手勢
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}
  • 拖拽手勢
- (void)pan:(UIPanGestureRecognizer *)pan
{

    // 獲取手指平移的偏移量
    CGPoint transP = [pan translationInView:_imageView];

    _imageView.transform = CGAffineTransformTranslate(_imageView.transform, transP.x, transP.y);

    // 復位
    [pan setTranslation:CGPointZero inView:_imageView];
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章