iOS中手勢(shì)的應(yīng)用

iOS設(shè)備現(xiàn)如今大受歡迎的最重要原因之一就在于其開創(chuàng)了觸控操作的潮流。發(fā)展到現(xiàn)在,無論是Android還是iPhone,現(xiàn)在APP與用戶進(jìn)行交互,基本上都是依賴于各種各樣的觸控事件。例如用戶對(duì)屏幕進(jìn)行了側(cè)滑,APP就需要對(duì)這個(gè)手勢(shì)進(jìn)行相應(yīng)的處理,給用戶一個(gè)反饋。這些相應(yīng)的事件就都是在UIResponder中定義的。

廣告插播的措不及防:如果您要是覺得這篇文章讓您有點(diǎn)收獲,隨手點(diǎn)個(gè)贊會(huì)讓俺興奮好久吶。

UIResponder大體有四類事件:觸摸、加速計(jì)、遠(yuǎn)程控制、按壓(iOS9.0以后出來的,3DTouch)。

觸摸事件.png

但是在iOS中不是任何對(duì)象都能處理事件,只有繼承了UIResponder的對(duì)象才能接收并處理事件。我們稱之為“響應(yīng)者對(duì)象”。

UIApplication、UIViewController、UIView都繼承自UIResponder,因此它們都是響應(yīng)者對(duì)象,都能夠接收并處理事件。

  • UIView繼承自UIResponder,因此所有的控件都是響應(yīng)者對(duì)象
  • UIWindow:是特殊的UIView,所以也是響應(yīng)者對(duì)象
  • UIApplication,所以也是響應(yīng)者對(duì)象

1. 四類事件的主要方法

有的童鞋可能分不清楚手勢(shì)當(dāng)中結(jié)束和取消的區(qū)別。舉個(gè)栗子,當(dāng)正在撫摸自己的愛機(jī)屏幕的時(shí)候,突然來了一個(gè)電話,這個(gè)“愛撫”的動(dòng)作就被臨時(shí)中斷了,這個(gè)時(shí)候就叫做“取消”,而不是結(jié)束。

1.1 觸摸事件

觸摸事件分成了四部分:開始、移動(dòng)、結(jié)束、取消。

-(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;

1.2 加速計(jì)事件

加速計(jì)事件分成了三部分:開始、結(jié)束、取消。也有人叫做運(yùn)動(dòng)事件,motion events。

-(void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event;
-(void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event;
-(void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event;

1.3 遠(yuǎn)程控制事件

收到控制事件:

-(void)remoteControlReceivedWithEvent:(UIEvent *)event;

1.4 按壓事件

按壓事件分成四部分:按壓開始、按壓改變、按壓結(jié)束、按壓取消。

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

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

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

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

2. 響應(yīng)者鏈

概念吶,我們就不說了,網(wǎng)上的文章應(yīng)該一搜一大堆。這里是比較理論化的知識(shí),是比較考驗(yàn)我們對(duì)于iOS中觸摸事件的理解深度的。這里我就只是用簡單的方式,寫一下自己對(duì)于這部分的理解。

根據(jù)第一部分的內(nèi)容,知道UIResponder有好多好多。用戶點(diǎn)擊屏幕之后,系統(tǒng)到底讓誰來響應(yīng)這個(gè)觸摸事件吶?例如用戶點(diǎn)了一個(gè)button,是應(yīng)該讓誰來處理呢?
UIButton肯定是放在一個(gè)UIView上面,UIView也肯定是放在一個(gè)Controller里面。這幾個(gè)都是響應(yīng)者對(duì)象,總不能讓大家一起給用戶反饋吧。系統(tǒng)這里的原則其實(shí)是越具體越好,也就是干活的肯定是最小的那個(gè)小兵兵。要是什么事情都讓UIApplication或者UIWindow干,還不讓它兩兒累死啊,那系統(tǒng)效率要低成神馬樣子。

最終找到這個(gè)干活的控件,我們學(xué)術(shù)上就叫做第一響應(yīng)者對(duì)象。找到了負(fù)責(zé)處理的按鈕之后如何給出相應(yīng)處理呢?大概過程就是這樣:

  • button嘗試處理事件。如果它不能處理事件,則將事件傳遞給其父視圖。
  • button的父視圖(superview)嘗試處理事件。如果這個(gè)父視圖還不能處理事件,則繼續(xù)將視圖繼續(xù)往上級(jí)傳。
  • 上層視圖(topmost view)會(huì)嘗試處理事件。如果這個(gè)上層視圖還是不能處理事件,則將事件傳遞給視圖所在的視圖控制器。
  • 視圖控制器會(huì)嘗試處理事件。如果這個(gè)視圖控制器不能處理事件,則將事件傳遞給窗口(window)對(duì)象。
  • 窗口(window)對(duì)象嘗試處理事件。如果不能處理,則將事件傳遞給UIApplication。
  • 如果UIApplication不能處理事件,則丟棄這個(gè)事件。就是白按嘍。

一次完整的觸摸事件的傳遞響應(yīng)的過程大概是這樣的: UIAppliction --> UIWindow -->遞歸找到最適合處理事件的控件-->控件調(diào)用touches方法-->判斷是否實(shí)現(xiàn)touches方法-->沒有實(shí)現(xiàn)默認(rèn)會(huì)將事件傳遞給上一個(gè)響應(yīng)者-->找到上一個(gè)響應(yīng)者。

對(duì)于第一響應(yīng)者,UIResponder提供了一系列方法,我們分別來介紹一下。

  1. 如果想判定一個(gè)響應(yīng)對(duì)象是否是第一響應(yīng)者,則可以使用以下方法:
  • (BOOL)isFirstResponder
  1. 如果我們希望將一個(gè)響應(yīng)對(duì)象作為第一響應(yīng)者,則可以使用以下方法:
  • (BOOL)becomeFirstResponder
  1. 一個(gè)響應(yīng)對(duì)象只有在當(dāng)前響應(yīng)者能放棄第一響應(yīng)者狀態(tài)(canResignFirstResponder)且自身能成為第一響應(yīng)者(canBecomeFirstResponder)時(shí)才會(huì)成為第一響應(yīng)者。換種說法就是,有人想當(dāng)部門老大,那也得現(xiàn)在的部門頭愿意放權(quán),并且這個(gè)人還有能力才能成為老大。
//判斷是否能夠成為第一響應(yīng)者
- (BOOL)canBecomeFirstResponder

//響應(yīng)者放棄第一響應(yīng)者身份
- (BOOL)resignFirstResponder
- (BOOL)canResignFirstResponder

這些方法大家用的都會(huì)比較多,特別是想讓文本輸入框獲取到焦點(diǎn)的時(shí)候。

3. 手勢(shì)識(shí)別功能(Gesture Recognizer)

  • 如果想監(jiān)聽一個(gè)view上面的觸摸事件,之前的做法是
    • 自定義一個(gè)view
    • 實(shí)現(xiàn)view的touches方法,在方法內(nèi)部實(shí)現(xiàn)具體處理代碼
  • 通過touches方法監(jiān)聽view觸摸事件,有很明顯的幾個(gè)缺點(diǎn)
    • 必須得自定義view
    • 由于是在view內(nèi)部的touches方法中監(jiān)聽觸摸事件,因此默認(rèn)情況下,無法讓其他外界對(duì)象監(jiān)聽view的觸摸事件
    • 不容易區(qū)分用戶的具體手勢(shì)行為- iOS 3.2之后,蘋果推出了手勢(shì)識(shí)別功能(Gesture Recognizer),在觸摸事件處理方面,大大簡化了開發(fā)者的開發(fā)難度

3.1手勢(shì)識(shí)別器(UIGestureRecognizer)

  • 為了完成手勢(shì)識(shí)別,必須借助于手勢(shì)識(shí)別器——UIGestureRecognizer
  • 利用UIGestureRecognizer,能輕松識(shí)別用戶在某個(gè)view上面做的一些常見手勢(shì)
  • UIGestureRecognizer是一個(gè)抽象類,定義了所有手勢(shì)的基本行為,使用它的子類才能處理具體的手勢(shì)
    • UITapGestureRecognizer(點(diǎn)按)
    • UIPinchGestureRecognizer(捏合,用于縮放)
    • UIPanGestureRecognizer(拖動(dòng))
    • UISwipeGestureRecognizer(輕掃)
    • UIRotationGestureRecognizer(旋轉(zhuǎn))
    • UILongPressGestureRecognizer(長按)

3.2 手勢(shì)識(shí)別的使用方法

  • 1.創(chuàng)建手勢(shì)識(shí)別實(shí)例
  • 2.設(shè)置手勢(shì)識(shí)別屬性,例如手指數(shù)量,方向等
  • 3.將手勢(shì)識(shí)別附加到指定的視圖之上
  • 4.編寫手勢(shì)觸發(fā)監(jiān)聽方法
  • 每一個(gè)手勢(shì)識(shí)別器的用法都差不多,比如UITapGestureRecognizer的使用步驟如下:
//創(chuàng)建手勢(shì)識(shí)別器對(duì)象
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] init];

//設(shè)置手勢(shì)識(shí)別器對(duì)象的具體屬性,例如連續(xù)敲擊2次
tap.numberOfTapsRequired = 2;
// 需要2根手指一起敲擊
tap.numberOfTouchesRequired = 2;

//添加手勢(shì)識(shí)別器到對(duì)應(yīng)的view上
[self.iconView addGestureRecognizer:tap];

//監(jiān)聽手勢(shì)的觸發(fā)
[tap addTarget:self action:@selector(tapIconView:)];

3.3手勢(shì)識(shí)別的枚舉

typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
    // 沒有觸摸事件發(fā)生,所有手勢(shì)識(shí)別的默認(rèn)狀態(tài)
    UIGestureRecognizerStatePossible,
    // 一個(gè)手勢(shì)已經(jīng)開始但尚未改變或者完成時(shí)
    UIGestureRecognizerStateBegan, (類似于 touchesBegan)
    // 手勢(shì)狀態(tài)改變
    UIGestureRecognizerStateChanged, (類似于 touchesMoved)
    // 手勢(shì)完成
    UIGestureRecognizerStateEnded, (類似于 touchesEnded)
    // 手勢(shì)取消,恢復(fù)至Possible狀態(tài)
    UIGestureRecognizerStateCancelled, (比如手指按下按鈕,然后從其他地方抬起)
    // 手勢(shì)失敗,恢復(fù)至Possible狀態(tài)
    UIGestureRecognizerStateFailed,
    // 識(shí)別到手勢(shì)識(shí)別
    UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded
};

4. 手勢(shì)的使用

4.1 長按手勢(shì)

  • 長按手勢(shì)一定要判斷狀態(tài),否則方法會(huì)在手勢(shì)開始和結(jié)束時(shí)分別調(diào)用!方法會(huì)被調(diào)用兩次!
- (void)addLongPressGesture
{
    //創(chuàng)建長按手勢(shì)識(shí)別并添加監(jiān)聽事件
    UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
    //    給圖片添加長按手勢(shì)
    [self.imageView addGestureRecognizer:longPress];
}

//識(shí)別到長按手勢(shì)后回調(diào)的方法
- (void)longPress:(UILongPressGestureRecognizer *)recognizer
{
    // 判斷手勢(shì)的狀態(tài),長按手勢(shì)一定要判斷狀態(tài),否則方法會(huì)在手勢(shì)開始和結(jié)束時(shí)分別調(diào)用!方法會(huì)被調(diào)用兩次!
    if (recognizer.state == UIGestureRecognizerStateBegan) {
        //動(dòng)畫  改變圖片的透明度
        [UIView animateWithDuration:0.5 animations:^{
            self.imageView.alpha = 0.5;
        } completion:^(BOOL finished) {
            [UIView animateWithDuration:0.5 animations:^{
                self.imageView.alpha = 1.0;
            }];
        }];
    }
}

4.2 清掃手勢(shì)

  • 如果要監(jiān)聽多個(gè)輕掃方向,需要添加多個(gè)輕掃手勢(shì)
  • 輕掃手勢(shì)默認(rèn)支持向右的掃動(dòng)方向
  • 因?yàn)檩p掃手勢(shì)要求用戶比較放松的掃動(dòng),因此最好不要將此手勢(shì)添加到某一個(gè)視圖上,會(huì)局限用戶的操作
- (void)addSwipeGesture
{
    // 如果要監(jiān)聽多個(gè)輕掃方向,需要添加多個(gè)輕掃手勢(shì)
    // 輕掃手勢(shì)默認(rèn)支持向右的掃動(dòng)方向
    
    //創(chuàng)建輕掃手勢(shì)識(shí)別并添加監(jiān)聽事件(默認(rèn)是向右掃動(dòng))
    UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipe:)];
    
    //創(chuàng)建輕掃手勢(shì)識(shí)別并添加監(jiān)聽事件
    UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipe:)];
    //direction 方向  向左輕掃
    swipeLeft.direction = UISwipeGestureRecognizerDirectionLeft;
    
    /**
     UISwipeGestureRecognizerDirectionRight  向右輕掃(默認(rèn))不設(shè)輕掃方向 就是向右
     UISwipeGestureRecognizerDirectionLeft  向左輕掃
     UISwipeGestureRecognizerDirectionUp    向上輕掃
     UISwipeGestureRecognizerDirectionDown  向下輕掃
     */
    
    
    // 因?yàn)檩p掃手勢(shì)要求用戶比較放松的掃動(dòng),因此最好不要將此手勢(shì)添加到某一個(gè)視圖上,會(huì)局限用戶的操作
    //    添加手勢(shì)
    [self.view addGestureRecognizer:swipe];
    [self.view addGestureRecognizer:swipeLeft];
}

//識(shí)別到輕掃手勢(shì)后回調(diào)的方法
- (void)swipe:(UISwipeGestureRecognizer *)recognizer
{
    //當(dāng)前獲取中心位置
    CGPoint from = self.imageView.center;
    //目標(biāo)
    CGPoint to;
    //向左輕掃
    if (recognizer.direction == UISwipeGestureRecognizerDirectionLeft) {
        to = CGPointMake(-from.x, from.y);
    } else {//向右輕掃
        to = CGPointMake(3 * from.x, from.y);
    }
    //動(dòng)畫移動(dòng)圖片
    [UIView animateWithDuration:0.5 animations:^{
        self.imageView.center = to;
    }completion:^(BOOL finished) {
        [UIView animateWithDuration:0.5 animations:^{
            self.imageView.center = from;
        }];
    }];
}

4.3 拖動(dòng)手勢(shì)

- (void)addPanGesture
{
    //創(chuàng)建拖動(dòng)手勢(shì) 并添加手勢(shì)的監(jiān)聽事件
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    
    //添加手勢(shì)
    [self.imageView addGestureRecognizer:pan];
}
//識(shí)別到拖動(dòng)手勢(shì)后回調(diào)的方法
- (void)pan:(UIPanGestureRecognizer *)recognizer
{
    //獲取手指按在圖片上的位置  以圖片左上角為原點(diǎn)
    CGPoint translation = [recognizer translationInView:self.imageView];
    //    移動(dòng)圖片
    recognizer.view.transform = CGAffineTransformTranslate(recognizer.view.transform, translation.x, translation.y);
    //給平移復(fù)位 因?yàn)樗窃谠谢A(chǔ)上當(dāng)前遞增平移 如果不復(fù)位  或清空他會(huì)越變?cè)酱?    [recognizer setTranslation:CGPointZero inView:self.imageView];
}

4.4 捏合手勢(shì)


- (void)addPinchGesture
{
    //創(chuàng)建縮放(捏合)手勢(shì) 并添加手勢(shì)的監(jiān)聽事件
    UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinch:)];
    //設(shè)置控制器為縮放手勢(shì)的代理  可以實(shí)現(xiàn)同時(shí)識(shí)別兩個(gè)手勢(shì)
    pinch.delegate = self;
    [self.imageView addGestureRecognizer:pinch];
}
//識(shí)別到 縮放(捏合)手勢(shì)后回調(diào)的方法
- (void)pinch:(UIPinchGestureRecognizer *)recognizer
{
    //綻放圖片
    recognizer.view.transform = CGAffineTransformScale(recognizer.view.transform, recognizer.scale, recognizer.scale);
    
    //將縮放比例復(fù)位
    recognizer.scale = 1.0;
}

4.5 旋轉(zhuǎn)手勢(shì)

- (void)addRotateGesture
{
    //創(chuàng)建縮放 旋轉(zhuǎn)并添加手勢(shì)的監(jiān)聽事件
    UIRotationGestureRecognizer *rotate = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(rotate:)];
    //設(shè)置控制器為縮放手勢(shì)的代理  可以實(shí)現(xiàn)同時(shí)識(shí)別兩個(gè)手勢(shì)
    rotate.delegate = self;
    //    添加手勢(shì)
    [self.imageView addGestureRecognizer:rotate];
}
//識(shí)別到旋轉(zhuǎn)手勢(shì)后的回調(diào)方法
- (void)rotate:(UIRotationGestureRecognizer *)recognizer
{
    //圖片旋轉(zhuǎn)
    recognizer.view.transform = CGAffineTransformRotate(recognizer.view.transform, recognizer.rotation);
    
    //將手勢(shì)識(shí)別的旋轉(zhuǎn)角度復(fù)位
    recognizer.rotation = 0.0;  //非常重要  角度也會(huì)疊加
}

4.6 單擊手勢(shì)

- (void)addTapGesture
{
    //創(chuàng)建縮放點(diǎn)按(單擊,點(diǎn)擊)并添加手勢(shì)的監(jiān)聽事件
    UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap)];
    //    添加手勢(shì)
    [self.imageView addGestureRecognizer:tapGesture];
    
}
//識(shí)別到手勢(shì)后的回調(diào)方法
- (void)tap
{
    NSLog(@"點(diǎn)我了");
}

4.7 手勢(shì)的總結(jié)

  • 一定記住設(shè)置完transform之后,需要將對(duì)應(yīng)的形變參數(shù)復(fù)位
  • 手勢(shì)識(shí)別,是單獨(dú)添加到某一個(gè)視圖上的
  • 如果要同時(shí)支持多個(gè)手勢(shì)識(shí)別,需要設(shè)置手勢(shì)識(shí)別的代理

是否支持多手勢(shì)觸摸的代理方法


- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

謝謝您看完這么長一篇文章,辛苦啦~!點(diǎn)個(gè)贊吧。哈哈??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容