一.手勢UIGestureRecognier簡介
iOS 3.2之後,蘋果推出了手勢識別功能(Gesture Recognizer),在觸摸事件處理方面,大大簡化了開發者的開發難度。利用UIGestureRecognizer,能輕松識別用戶在某個view上面做的一些常見手勢。UIGestureRecognizer是一個抽象類,對iOS中的事件傳遞機制面向應用進行封裝,將手勢消息的傳遞抽象為了對象。其中定義了所有手勢的基本行為,使用它的子類才能處理具體的手勢。
二.手勢的抽象類——UIGestureRecognizer
UIGestureRecognizer將一些和手勢操作相關的方法抽象了出來,但它本身並不實現什麼手勢,因此,在開發中,我們一般不會直接使用UIGestureRecognizer的對象,而是通過其子類進行實例化,iOS系統給我們提供了許多用於實例的子類,這些我們後面再說,我們先來看一下,UIGestureRecognizer中抽象出了哪些方法。
1.初始化方法
UIGestureRecognizer類為其子類准備好了一個統一的初始化方法,無論什麼樣的手勢動作,其執行的結果都是一樣的:觸發一個方法,可以使用下面的方法進行統一的初始化:
-(instancetype)initWithTarget:(nullable id)target action:(nullable SEL)action
當然,如果我們使用alloc-init的方式,也是可以的,下面的方法可以為手勢添加觸發的selector:
-(void)addTarget:(id)target action:(SEL)action;
與之相對應的,我們也可以將一個selector從其手勢對象上移除:
-(void)removeTarget:(nullable id)target action:(nullable SEL)action;
因為addTarget方式的存在,iOS系統允許一個手勢對象可以添加多個selector觸發方法,並且觸發的時候,所有添加的selector都會被執行,我們以點擊手勢示例如下:
-(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__);
}
點擊屏幕,打印內容如下,說明兩個方法都觸發了

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

而當我們將pan.cancelsTouchesInView = YES屬性設置為YES時,打印結果如下

我們發現touchesMoved的方法仍然被調用了,這是為什麼呢?這就涉及到第二個屬性delaysTouchesBegan,這是因為手勢識別是有一個過程的,拖拽手勢需要一個很小的手指移動的過程才能被識別為拖拽手勢,而在一個手勢觸發之前,是會一並發消息給事件傳遞鏈的,所以才會有最開始的幾個touchMoved方法被調用,當識別出拖拽手勢以後,就會終止touch事件的傳遞。
delaysTouchesBgan屬性用於控制這個消息的傳遞時機,默認這個屬性為NO,此時在觸摸開始的時候,就會發消息給事件傳遞鏈,如果我們設置為YES,在觸摸沒有被識別失敗前,都不會給事件傳遞鏈發送消息。
因此當我們設置pan.delaysTouchesBegan = YES;時打印內容如下<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxpbWcgYWx0PQ=="這裡寫圖片描述" src="/uploadfile/Collfiles/20161012/201610120919301097.png" title="\" />
因為此時在拖拽手勢識別失敗之前,都不會給時間傳遞鏈發送消息,所以就不會在調用touchesMoved觸發事件了
而delaysTouchesEnded屬性默認是YES,當設為YES時在手勢識別結束後,會等待一個很短的時間,如果沒有接收到新的手勢識別任務,才會發送touchesEnded消息到事件傳遞鏈,設置為NO之後會立刻發送touchesEnded消息到事件傳遞鏈我們同樣來看一個例子:
- (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 *)touches withEvent:(UIEvent *)event{
NSLog(@"touchBegan手勢開始");
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(@"touchEnd手勢觸發結束");
}
-(void)tap:(UITapGestureRecognizer *)tap
{
NSLog(@"tap手勢觸發");
}
當tap.delaysTouchesEnded = NO;時,輕拍三下屏幕,打印如下

我們發現我們每點擊一下,都會立即發送touchesEnded消息到事件傳遞鏈。
而當tap.delaysTouchesEnded = YES;時,輕拍三下屏幕,打印如下

等三下輕拍手勢識別結束後,才會發送消息到事件傳遞鏈。
3.2重點方法詳解-手勢間的互斥處理
同一個View上是可以添加多個手勢對象的,默認這些手勢是互斥的,一個手勢觸發了就會默認屏蔽其他相似的手勢動作。比如,單擊和雙擊並存時,如果不做處理,它就只能發送出單擊的消息。為了能夠識別出雙擊手勢,就需要用下面的方法一個特殊處理邏輯,即先判斷手勢是否是雙擊,在雙擊失效的情況下作為單擊手勢處理。
-(void)requireGestureRecognizerToFail:(UIGestureRecognizer*)otherGestureRecognizer;
[A requireGestureRecognizerToFail:B]它可以指定當A手勢發生時,即便A已經滿足條件了,也不會立刻觸發,會等到指定的手勢B確定失敗之後才觸發。
看一個例子
-(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];
//當tap2手勢觸發失敗時才會觸發tap1手勢
[tap1 requireGestureRecognizerToFail:tap2];
}
-(void)tap1:(UITapGestureRecognizer*)tap
{
NSLog( "tap1手勢觸發");
}
-(void)tap2:(UITapGestureRecognizer*)tap
{
NSLog( "tap2手勢觸發");
}
3.3.UIGestureRecognizerDelegate
前面我們提到過關於手勢對象的協議代理,通過代理的回調,我們可以進行自定義手勢,也可以處理一些復雜的手勢關系,其中方法如下:
//手指觸摸屏幕後回調的方法,返回NO則不再進行手勢識別,方法觸發等
-(BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer shouldReceiveTouch:(UITouch*)touch;
//開始進行手勢識別時調用的方法,返回NO則結束,不再觸發手勢
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gestureRecognizer;
//是否支持多時候觸發,返回YES,則可以多個手勢一起觸發方法,返回NO則為互斥
-(BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer;
//下面這個兩個方法也是用來控制手勢的互斥執行的
//這個方法返回YES,第一個手勢和第二個互斥時,第一個會失效
-(BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);
//這個方法返回YES,第一個和第二個互斥時,第二個會失效
-(BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);
三.UIGestureRecognizer子類及子類屬性
除了UIGestureRecognizer中的方法和屬性是所有子類通用的之外,UIGestureRecognizer子類中分別有不同的屬性和方法來對應不同的手勢。
1.點擊手勢——UITapGestureRecognizer
點擊手勢十分簡單,支持單擊和多次點擊,在我們手指觸摸屏幕並抬起手指時會進行觸發,其中有如下兩個屬性我們可以進行設置:
//設置點擊次數,默認為單擊
property(nonatomic)NSUInteger numberOfTapsRequired;
//設置同時點擊的手指數
property(nonatomic)NSUInteger numberOfTouchesRequired;
2.捏合手勢——UIPinchGestureRecognizer
捏合手勢是當我們雙指捏合和擴張會觸發動作的手勢,我們可以設置的屬性如下:
//設置縮放比例
property(nonatomic)CGFloat scale;
//設置捏合速度
property(nonatomic,readonly)CGFloat velocity;
3.拖拽手勢——UIPanGestureRecognzer
當我們點中視圖進行慢速拖拽時會觸發拖拽手勢的方法。
//設置觸發拖拽的最少觸摸點,默認為1
property(nonatomic)NSUInteger minimumNumberOfTouches;
//設置觸發拖拽的最多觸摸點
property(nonatomic)NSUInteger maximumNumberOfTouches;
//獲取當前位置
-(CGPoint)translationInView:(nullable UIView*)view;
//設置當前位置
-(void)setTranslation:(CGPoint)translation inView:(nullable UIView*)view;
//設置拖拽速度
-(CGPoint)velocityInView:(nullable UIView*)view;
4.滑動手勢——UISwipeGestureRecognizer
滑動手勢和拖拽手勢的不同之處在於滑動手勢更快,而拖拽比較慢。
//設置觸發滑動手勢的觸摸點數
property(nonatomic)NSUInteger numberOfTouchesRequired;
//設置滑動方向
property(nonatomic)UISwipeGestureRecognizerDirection direction;
//枚舉如下
typedef NS_OPTIONS(NSUInteger,UISwipeGestureRecognizerDirection){
UISwipeGestureRecognizerDirectionRight=1<<0,
UISwipeGestureRecognizerDirectionLeft=1<<1,
UISwipeGestureRecognizerDirectionUp=1<<2,
UISwipeGestureRecognizerDirectionDown=1<<3
};
5.旋轉手勢——UIRotationGestureRecognizer
進行旋轉動作時觸發手勢方法。
//設置旋轉角度
property(nonatomic)CGFloat rotation;
//設置旋轉速度
property(nonatomic,readonly)CGFloat velocity;
6.長按手勢——UILongPressGestureRecognizer
進行長按的時候觸發的手勢方法。
//設置觸發前的點擊次數
property(nonatomic)NSUInteger numberOfTapsRequired;
//設置觸發的觸摸點數
property(nonatomic)NSUInteger numberOfTouchesRequired;
//設置最短的長按時間
property(nonatomic)CFTimeInterval minimumPressDuration;
//設置在按觸時時允許移動的最大距離默認為10像素
property(nonatomic)CGFloat allowableMovement;
7.自定義手勢
自定義手勢繼承:UIGestureRecognizer,實現下面的方法,在以下方法中判斷自定義手勢是否實現。
–touchesBegan:withEvent:
–touchesMoved:withEvent:
–touchesEnded:withEvent:
-touchesCancelled:withEvent:
注意.m文件中需要引入#import