天天看點

No5 觸摸事件

一 Modal

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

modal:會把新控制器的view添加視窗上,但是不會修改視窗的根控制器

modal:會把新控制器強引用,誰modal,誰就強引用,為什麼要強引用,如果不強引用,新建立的控制器就會被銷毀,就不能處理modal出來界面的業務邏輯.

  • modal原了解析
// 從下往上鑽的動畫
 // 首先讓oneVc的view顯示在視窗的底部
    oneVc.view.transform = CGAffineTransformMakeTranslation(, keyWindow.bounds.size.height);   
 // 動畫,往上移動,還原形變
    [UIView animateWithDuration: 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…
  • 執行個體:
    No5 觸摸事件
  • 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 <= ) 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 - ; i >= ; 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 = ;   
}
           
  • 縮放手勢
- (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 = ;
}
           
  • 如果同時添加縮放和旋轉手勢,代理方法
// 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];
}