一. 手勢(shì)UIGestureRecognier簡介
iOS 3.2之后,蘋果推出了手勢(shì)識(shí)別功能(Gesture Recognizer),在觸摸事件處理方面,大大簡化了開發(fā)者的開發(fā)難度。利用UIGestureRecognizer,能輕松識(shí)別用戶在某個(gè)view上面做的一些常見手勢(shì)。UIGestureRecognizer是一個(gè)抽象類,對(duì)iOS中的事件傳遞機(jī)制面向應(yīng)用進(jìn)行封裝,將手勢(shì)消息的傳遞抽象為了對(duì)象。其中定義了所有手勢(shì)的基本行為,使用它的子類才能處理具體的手勢(shì)。
二. 手勢(shì)的抽象類——UIGestureRecognizer
UIGestureRecognizer將一些和手勢(shì)操作相關(guān)的方法抽象了出來,但它本身并不實(shí)現(xiàn)什么手勢(shì),因此,在開發(fā)中,我們一般不會(huì)直接使用UIGestureRecognizer的對(duì)象,而是通過其子類進(jìn)行實(shí)例化,iOS系統(tǒng)給我們提供了許多用于實(shí)例的子類,這些我們后面再說,我們先來看一下,UIGestureRecognizer中抽象出了哪些方法。
1. 初始化方法
UIGestureRecognizer類為其子類準(zhǔn)備好了一個(gè)統(tǒng)一的初始化方法,無論什么樣的手勢(shì)動(dòng)作,其執(zhí)行的結(jié)果都是一樣的:觸發(fā)一個(gè)方法,可以使用下面的方法進(jìn)行統(tǒng)一的初始化:
- (instancetype)initWithTarget:(nullable id)target action:(nullable SEL)action
當(dāng)然,如果我們使用alloc-init的方式,也是可以的,下面的方法可以為手勢(shì)添加觸發(fā)的selector:
- (void)addTarget:(id)target action:(SEL)action;
與之相對(duì)應(yīng)的,我們也可以將一個(gè)selector從其手勢(shì)對(duì)象上移除:
- (void)removeTarget:(nullable id)target action:(nullable SEL)action;
因?yàn)閍ddTarget方式的存在,iOS系統(tǒng)允許一個(gè)手勢(shì)對(duì)象可以添加多個(gè)selector觸發(fā)方法,并且觸發(fā)的時(shí)候,所有添加的selector都會(huì)被執(zhí)行,我們以點(diǎn)擊手勢(shì)示例如下:
- (void)viewDidLoad {
[super viewDidLoad];
UITapGestureRecognizer *tap1 = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tap1:)];
[tap1 addTarget:self action:@selector(tap2:)];
[self.view addGestureRecognizer:tap1];
}
-(void)tap1:(UITapGestureRecognizer *)tap
{
NSLog(@"%s",__func__);
}
-(void)tap2:(UITapGestureRecognizer *)tap
{
NSLog(@"%s",__func__);
}
點(diǎn)擊屏幕,打印內(nèi)容如下,說明兩個(gè)方法都觸發(fā)了

2. 手勢(shì)狀態(tài)
UIgestureRecognizer類中有如下一個(gè)屬性,里面枚舉了一些手勢(shì)的當(dāng)前狀態(tài):
@property(nonatomic,readonly) UIGestureRecognizerState state;
枚舉值如下:
typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
UIGestureRecognizerStatePossible, // 默認(rèn)的狀態(tài),這個(gè)時(shí)候的手勢(shì)并沒有具體的情形狀態(tài)
UIGestureRecognizerStateBegan, // 手勢(shì)開始被識(shí)別的狀態(tài)
UIGestureRecognizerStateChanged, // 手勢(shì)識(shí)別發(fā)生改變的狀態(tài)
UIGestureRecognizerStateEnded, // 手勢(shì)識(shí)別結(jié)束,將會(huì)執(zhí)行觸發(fā)的方法
UIGestureRecognizerStateCancelled, // 手勢(shì)識(shí)別取消
UIGestureRecognizerStateFailed, // 識(shí)別失敗,方法將不會(huì)被調(diào)用
UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded
};
3. 常用屬性和方法
//手勢(shì)代理 代理中有一些手勢(shì)觸發(fā)的方法,后面拿出來詳細(xì)說明
@property(nullable,nonatomic,weak) id <UIGestureRecognizerDelegate> delegate;
//設(shè)置手勢(shì)是否有效
@property(nonatomic, getter=isEnabled) BOOL enabled;
//獲取手勢(shì)所在的View
@property(nullable, nonatomic,readonly) UIView *view;
//默認(rèn)是YES。當(dāng)識(shí)別到手勢(shì)的時(shí)候,終止touchesCancelled:withEvent:或pressesCancelled:withEvent:發(fā)送的所有觸摸事件。
@property(nonatomic) BOOL cancelsTouchesInView;
//默認(rèn)為NO ,在觸摸開始的時(shí)候,就會(huì)發(fā)消息給事件傳遞鏈,如果設(shè)置為YES,在觸摸沒有被識(shí)別失敗前,都不會(huì)給事件傳遞鏈發(fā)送消息。
@property(nonatomic) BOOL delaysTouchesBegan;
//默認(rèn)為YES 。這個(gè)屬性設(shè)置手勢(shì)識(shí)別結(jié)束后,是立刻發(fā)送touchesEnded或pressesEnded消息到事件傳遞鏈或者等待一個(gè)很短的時(shí)間后,如果沒有接收到新的手勢(shì)識(shí)別任務(wù),再發(fā)送。
@property(nonatomic) BOOL delaysTouchesEnded;
@property(nonatomic, copy) NSArray<NSNumber *> *allowedTouchTypes NS_AVAILABLE_IOS(9_0); // Array of UITouchType's as NSNumbers.
@property(nonatomic, copy) NSArray<NSNumber *> *allowedPressTypes NS_AVAILABLE_IOS(9_0); // Array of UIPressTypes as NSNumbers.
//[A requireGestureRecognizerToFail:B]手勢(shì)互斥 它可以指定當(dāng)A手勢(shì)發(fā)生時(shí),即便A已經(jīng)滿足條件了,也不會(huì)立刻觸發(fā),會(huì)等到指定的手勢(shì)B確定失敗之后才觸發(fā)。
- (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer;
//獲取當(dāng)前觸摸的點(diǎn)
- (CGPoint)locationInView:(nullable UIView*)view;
//設(shè)置觸摸點(diǎn)數(shù)
- (NSUInteger)numberOfTouches;
//獲取某一個(gè)觸摸點(diǎn)的觸摸位置
- (CGPoint)locationOfTouch:(NSUInteger)touchIndex inView:(nullable UIView*)view;
3.1 個(gè)別屬性詳解
其中幾個(gè)BOOL值的屬性,對(duì)于手勢(shì)觸發(fā)的控制也十分重要,下面我們舉個(gè)栗子來詳細(xì)說明一下以下三個(gè)方法。
@property(nonatomic) BOOL cancelsTouchesInView;
@property(nonatomic) BOOL delaysTouchesBegan;
@property(nonatomic) BOOL delaysTouchesEnded;
- (void)viewDidLoad {
[super viewDidLoad];
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];
pan.cancelsTouchesInView = NO;
// pan.delaysTouchesBegan = YES;
[self.view addGestureRecognizer:pan];
}
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"touchMoved手勢(shì)觸發(fā)");
}
-(void)pan:(UIPanGestureRecognizer *)pan{
NSLog(@"pan手勢(shì)觸發(fā)");
}
pan.cancelsTouchesInView屬性默認(rèn)設(shè)置為YES,如果識(shí)別到了手勢(shì),系統(tǒng)將會(huì)發(fā)送touchesCancelled:withEvent:消息在其時(shí)間傳遞鏈上,終止觸摸事件的傳遞,也就是說默認(rèn)當(dāng)識(shí)別到手勢(shì)時(shí),touch事件傳遞的方法將被終止而不執(zhí)行,如果設(shè)置為NO,touch事件傳遞的方法仍然會(huì)被執(zhí)行,上例中我們使用了拖拽手勢(shì)和touchesMoved兩個(gè)觸發(fā)方式,當(dāng)我們把cancelTouchesInView設(shè)置為NO時(shí),在屏幕上滑動(dòng),兩種方式都在觸發(fā),打印如下:

而當(dāng)我們將pan.cancelsTouchesInView = YES屬性設(shè)置為YES時(shí),打印結(jié)果如下

我們發(fā)現(xiàn)touchesMoved的方法仍然被調(diào)用了,這是為什么呢?這就涉及到第二個(gè)屬性delaysTouchesBegan,這是因?yàn)槭謩?shì)識(shí)別是有一個(gè)過程的,拖拽手勢(shì)需要一個(gè)很小的手指移動(dòng)的過程才能被識(shí)別為拖拽手勢(shì),而在一個(gè)手勢(shì)觸發(fā)之前,是會(huì)一并發(fā)消息給事件傳遞鏈的,所以才會(huì)有最開始的幾個(gè)touchMoved方法被調(diào)用,當(dāng)識(shí)別出拖拽手勢(shì)以后,就會(huì)終止touch事件的傳遞。
delaysTouchesBgan屬性用于控制這個(gè)消息的傳遞時(shí)機(jī),默認(rèn)這個(gè)屬性為NO,此時(shí)在觸摸開始的時(shí)候,就會(huì)發(fā)消息給事件傳遞鏈,如果我們?cè)O(shè)置為YES,在觸摸沒有被識(shí)別失敗前,都不會(huì)給事件傳遞鏈發(fā)送消息。
因此當(dāng)我們?cè)O(shè)置pan.delaysTouchesBegan = YES;時(shí)打印內(nèi)容如下

因?yàn)榇藭r(shí)在拖拽手勢(shì)識(shí)別失敗之前,都不會(huì)給時(shí)間傳遞鏈發(fā)送消息,所以就不會(huì)在調(diào)用touchesMoved觸發(fā)事件了
而delaysTouchesEnded屬性默認(rèn)是YES,當(dāng)設(shè)為YES時(shí)在手勢(shì)識(shí)別結(jié)束后,會(huì)等待一個(gè)很短的時(shí)間,如果沒有接收到新的手勢(shì)識(shí)別任務(wù),才會(huì)發(fā)送touchesEnded消息到事件傳遞鏈,設(shè)置為NO之后會(huì)立刻發(fā)送touchesEnded消息到事件傳遞鏈我們同樣來看一個(gè)例子:
- (void)viewDidLoad {
[super viewDidLoad];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tap:)];
tap.numberOfTapsRequired = 3;
// tap.cancelsTouchesInView = NO;
// tap.delaysTouchesBegan = YES;
tap.delaysTouchesEnded = NO;
[self.view addGestureRecognizer:tap];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"touchBegan手勢(shì)開始");
}
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"touchEnd手勢(shì)觸發(fā)結(jié)束");
}
-(void)tap:(UITapGestureRecognizer *)tap
{
NSLog(@"tap手勢(shì)觸發(fā)");
}
當(dāng)tap.delaysTouchesEnded = NO;時(shí),輕拍三下屏幕,打印如下

我們發(fā)現(xiàn)我們每點(diǎn)擊一下,都會(huì)立即發(fā)送touchesEnded消息到事件傳遞鏈。
而當(dāng)
tap.delaysTouchesEnded = YES;時(shí),輕拍三下屏幕,打印如下
等三下輕拍手勢(shì)識(shí)別結(jié)束后,才會(huì)發(fā)送消息到事件傳遞鏈。
3.2 重點(diǎn)方法詳解-手勢(shì)間的互斥處理
同一個(gè)View上是可以添加多個(gè)手勢(shì)對(duì)象的,默認(rèn)這些手勢(shì)是互斥的,一個(gè)手勢(shì)觸發(fā)了就會(huì)默認(rèn)屏蔽其他相似的手勢(shì)動(dòng)作。比如,單擊和雙擊并存時(shí),如果不做處理,它就只能發(fā)送出單擊的消息。為了能夠識(shí)別出雙擊手勢(shì),就需要用下面的方法一個(gè)特殊處理邏輯,即先判斷手勢(shì)是否是雙擊,在雙擊失效的情況下作為單擊手勢(shì)處理。
- (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer;
[A requireGestureRecognizerToFail:B] 它可以指定當(dāng)A手勢(shì)發(fā)生時(shí),即便A已經(jīng)滿足條件了,也不會(huì)立刻觸發(fā),會(huì)等到指定的手勢(shì)B確定失敗之后才觸發(fā)。
看一個(gè)例子
- (void)viewDidLoad {
[super viewDidLoad];
UITapGestureRecognizer *tap1 = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tap1:)];
tap1.numberOfTapsRequired = 1;
[self.view addGestureRecognizer:tap1];
UITapGestureRecognizer *tap2 = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tap2:)];
tap2.numberOfTapsRequired = 2;
[self.view addGestureRecognizer:tap2];
//當(dāng)tap2手勢(shì)觸發(fā)失敗時(shí)才會(huì)觸發(fā)tap1手勢(shì)
[tap1 requireGestureRecognizerToFail:tap2];
}
-(void)tap1:(UITapGestureRecognizer *)tap
{
NSLog(@"tap1手勢(shì)觸發(fā)");
}
-(void)tap2:(UITapGestureRecognizer *)tap
{
NSLog(@"tap2手勢(shì)觸發(fā)");
}
3.3. UIGestureRecognizerDelegate
前面我們提到過關(guān)于手勢(shì)對(duì)象的協(xié)議代理,通過代理的回調(diào),我們可以進(jìn)行自定義手勢(shì),也可以處理一些復(fù)雜的手勢(shì)關(guān)系,其中方法如下:
//手指觸摸屏幕后回調(diào)的方法,返回NO則不再進(jìn)行手勢(shì)識(shí)別,方法觸發(fā)等
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;
//開始進(jìn)行手勢(shì)識(shí)別時(shí)調(diào)用的方法,返回NO則結(jié)束,不再觸發(fā)手勢(shì)
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;
//是否支持多時(shí)候觸發(fā),返回YES,則可以多個(gè)手勢(shì)一起觸發(fā)方法,返回NO則為互斥
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
//下面這個(gè)兩個(gè)方法也是用來控制手勢(shì)的互斥執(zhí)行的
//這個(gè)方法返回YES,第一個(gè)手勢(shì)和第二個(gè)互斥時(shí),第一個(gè)會(huì)失效
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);
//這個(gè)方法返回YES,第一個(gè)和第二個(gè)互斥時(shí),第二個(gè)會(huì)失效
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);
三. UIGestureRecognizer子類及子類屬性
除了UIGestureRecognizer中的方法和屬性是所有子類通用的之外,UIGestureRecognizer子類中分別有不同的屬性和方法來對(duì)應(yīng)不同的手勢(shì)。
1. 點(diǎn)擊手勢(shì)——UITapGestureRecognizer
點(diǎn)擊手勢(shì)十分簡單,支持單擊和多次點(diǎn)擊,在我們手指觸摸屏幕并抬起手指時(shí)會(huì)進(jìn)行觸發(fā),其中有如下兩個(gè)屬性我們可以進(jìn)行設(shè)置:
//設(shè)置點(diǎn)擊次數(shù),默認(rèn)為單擊
@property (nonatomic) NSUInteger numberOfTapsRequired;
//設(shè)置同時(shí)點(diǎn)擊的手指數(shù)
@property (nonatomic) NSUInteger numberOfTouchesRequired;
2. 捏合手勢(shì)——UIPinchGestureRecognizer
捏合手勢(shì)是當(dāng)我們雙指捏合和擴(kuò)張會(huì)觸發(fā)動(dòng)作的手勢(shì),我們可以設(shè)置的屬性如下:
//設(shè)置縮放比例
@property (nonatomic) CGFloat scale;
//設(shè)置捏合速度
@property (nonatomic,readonly) CGFloat velocity;
3. 拖拽手勢(shì)——UIPanGestureRecognzer
當(dāng)我們點(diǎn)中視圖進(jìn)行慢速拖拽時(shí)會(huì)觸發(fā)拖拽手勢(shì)的方法。
//設(shè)置觸發(fā)拖拽的最少觸摸點(diǎn),默認(rèn)為1
@property (nonatomic) NSUInteger minimumNumberOfTouches;
//設(shè)置觸發(fā)拖拽的最多觸摸點(diǎn)
@property (nonatomic) NSUInteger maximumNumberOfTouches;
//獲取當(dāng)前位置
- (CGPoint)translationInView:(nullable UIView *)view;
//設(shè)置當(dāng)前位置
- (void)setTranslation:(CGPoint)translation inView:(nullable UIView *)view;
//設(shè)置拖拽速度
- (CGPoint)velocityInView:(nullable UIView *)view;
4. 滑動(dòng)手勢(shì)——UISwipeGestureRecognizer
滑動(dòng)手勢(shì)和拖拽手勢(shì)的不同之處在于滑動(dòng)手勢(shì)更快,而拖拽比較慢。
//設(shè)置觸發(fā)滑動(dòng)手勢(shì)的觸摸點(diǎn)數(shù)
@property(nonatomic) NSUInteger numberOfTouchesRequired;
//設(shè)置滑動(dòng)方向
@property(nonatomic) UISwipeGestureRecognizerDirection direction;
//枚舉如下
typedef NS_OPTIONS(NSUInteger, UISwipeGestureRecognizerDirection) {
UISwipeGestureRecognizerDirectionRight = 1 << 0,
UISwipeGestureRecognizerDirectionLeft = 1 << 1,
UISwipeGestureRecognizerDirectionUp = 1 << 2,
UISwipeGestureRecognizerDirectionDown = 1 << 3
};
5. 旋轉(zhuǎn)手勢(shì)——UIRotationGestureRecognizer
進(jìn)行旋轉(zhuǎn)動(dòng)作時(shí)觸發(fā)手勢(shì)方法。
//設(shè)置旋轉(zhuǎn)角度
@property (nonatomic) CGFloat rotation;
//設(shè)置旋轉(zhuǎn)速度
@property (nonatomic,readonly) CGFloat velocity;
6. 長按手勢(shì)——UILongPressGestureRecognizer
進(jìn)行長按的時(shí)候觸發(fā)的手勢(shì)方法。
//設(shè)置觸發(fā)前的點(diǎn)擊次數(shù)
@property (nonatomic) NSUInteger numberOfTapsRequired;
//設(shè)置觸發(fā)的觸摸點(diǎn)數(shù)
@property (nonatomic) NSUInteger numberOfTouchesRequired;
//設(shè)置最短的長按時(shí)間
@property (nonatomic) CFTimeInterval minimumPressDuration;
//設(shè)置在按觸時(shí)時(shí)允許移動(dòng)的最大距離 默認(rèn)為10像素
@property (nonatomic) CGFloat allowableMovement;
7. 自定義手勢(shì)
自定義手勢(shì)繼承:UIGestureRecognizer,實(shí)現(xiàn)下面的方法,在以下方法中判斷自定義手勢(shì)是否實(shí)現(xiàn)。
– touchesBegan:withEvent:
– touchesMoved:withEvent:
– touchesEnded:withEvent:
- touchesCancelled:withEvent:
注意.m文件中需要引入#import <UIKit/UIGestureRecognizerSubclass.h>。
關(guān)于iOS-UITouch事件處理過程可以看這篇文章iOS-UITouch事件處理詳解
?本文借鑒了很多前輩的文章,如果有不對(duì)的地方請(qǐng)指正,歡迎大家一起交流學(xué)習(xí) xx_cc 。