一. 手勢UIGestureRecognier簡介
iOS 3.2之后另患,蘋果推出了手勢識別功能(Gesture Recognizer),在觸摸事件處理方面蛾绎,大大簡化了開發(fā)者的開發(fā)難度昆箕。利用UIGestureRecognizer,能輕松識別用戶在某個view上面做的一些常見手勢租冠。UIGestureRecognizer是一個抽象類鹏倘,對iOS中的事件傳遞機制面向應用進行封裝,將手勢消息的傳遞抽象為了對象顽爹。其中定義了所有手勢的基本行為纤泵,使用它的子類才能處理具體的手勢。
二. 手勢的抽象類——UIGestureRecognizer
UIGestureRecognizer將一些和手勢操作相關的方法抽象了出來镜粤,但它本身并不實現(xiàn)什么手勢捏题,因此,在開發(fā)中肉渴,我們一般不會直接使用UIGestureRecognizer的對象公荧,而是通過其子類進行實例化,iOS系統(tǒng)給我們提供了許多用于實例的子類同规,這些我們后面再說循狰,我們先來看一下窟社,UIGestureRecognizer中抽象出了哪些方法。
1. 初始化方法
UIGestureRecognizer類為其子類準備好了一個統(tǒng)一的初始化方法绪钥,無論什么樣的手勢動作灿里,其執(zhí)行的結果都是一樣的:觸發(fā)一個方法,可以使用下面的方法進行統(tǒng)一的初始化:
- (instancetype)initWithTarget:(nullable id)target action:(nullable SEL)action
當然昧识,如果我們使用alloc-init的方式钠四,也是可以的盗扒,下面的方法可以為手勢添加觸發(fā)的selector:
- (void)addTarget:(id)target action:(SEL)action;
與之相對應的跪楞,我們也可以將一個selector從其手勢對象上移除:
- (void)removeTarget:(nullable id)target action:(nullable SEL)action;
因為addTarget方式的存在,iOS系統(tǒng)允許一個手勢對象可以添加多個selector觸發(fā)方法侣灶,并且觸發(fā)的時候甸祭,所有添加的selector都會被執(zhí)行,我們以點擊手勢示例如下:
- (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__);
}
點擊屏幕褥影,打印內容如下池户,說明兩個方法都觸發(fā)了
2. 手勢狀態(tài)
UIgestureRecognizer類中有如下一個屬性,里面枚舉了一些手勢的當前狀態(tài):
@property(nonatomic,readonly) UIGestureRecognizerState state;
枚舉值如下:
typedef NS_ENUM(NSInteger, UIGestureRecognizerState) {
UIGestureRecognizerStatePossible, // 默認的狀態(tài)凡怎,這個時候的手勢并沒有具體的情形狀態(tài)
UIGestureRecognizerStateBegan, // 手勢開始被識別的狀態(tài)
UIGestureRecognizerStateChanged, // 手勢識別發(fā)生改變的狀態(tài)
UIGestureRecognizerStateEnded, // 手勢識別結束校焦,將會執(zhí)行觸發(fā)的方法
UIGestureRecognizerStateCancelled, // 手勢識別取消
UIGestureRecognizerStateFailed, // 識別失敗,方法將不會被調用
UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded
};
3. 常用屬性和方法
//手勢代理 代理中有一些手勢觸發(fā)的方法统倒,后面拿出來詳細說明
@property(nullable,nonatomic,weak) id <UIGestureRecognizerDelegate> delegate;
//設置手勢是否有效
@property(nonatomic, getter=isEnabled) BOOL enabled;
//獲取手勢所在的View
@property(nullable, nonatomic,readonly) UIView *view;
//默認是YES寨典。當識別到手勢的時候,終止touchesCancelled:withEvent:或pressesCancelled:withEvent:發(fā)送的所有觸摸事件房匆。
@property(nonatomic) BOOL cancelsTouchesInView;
//默認為NO ,在觸摸開始的時候耸成,就會發(fā)消息給事件傳遞鏈,如果設置為YES浴鸿,在觸摸沒有被識別失敗前井氢,都不會給事件傳遞鏈發(fā)送消息。
@property(nonatomic) BOOL delaysTouchesBegan;
//默認為YES 岳链。這個屬性設置手勢識別結束后花竞,是立刻發(fā)送touchesEnded或pressesEnded消息到事件傳遞鏈或者等待一個很短的時間后,如果沒有接收到新的手勢識別任務掸哑,再發(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]手勢互斥 它可以指定當A手勢發(fā)生時,即便A已經(jīng)滿足條件了举户,也不會立刻觸發(fā)烤宙,會等到指定的手勢B確定失敗之后才觸發(fā)。
- (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer;
//獲取當前觸摸的點
- (CGPoint)locationInView:(nullable UIView*)view;
//設置觸摸點數(shù)
- (NSUInteger)numberOfTouches;
//獲取某一個觸摸點的觸摸位置
- (CGPoint)locationOfTouch:(NSUInteger)touchIndex inView:(nullable UIView*)view;
3.1 個別屬性詳解
其中幾個BOOL值的屬性俭嘁,對于手勢觸發(fā)的控制也十分重要躺枕,下面我們舉個栗子來詳細說明一下以下三個方法。
@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手勢觸發(fā)");
}
-(void)pan:(UIPanGestureRecognizer *)pan{
NSLog(@"pan手勢觸發(fā)");
}
pan.cancelsTouchesInView
屬性默認設置為YES,如果識別到了手勢拐云,系統(tǒng)將會發(fā)送touchesCancelled:withEvent:消息在其時間傳遞鏈上罢猪,終止觸摸事件的傳遞,也就是說默認當識別到手勢時叉瘩,touch事件傳遞的方法將被終止而不執(zhí)行膳帕,如果設置為NO,touch事件傳遞的方法仍然會被執(zhí)行薇缅,上例中我們使用了拖拽手勢和touchesMoved兩個觸發(fā)方式危彩,當我們把cancelTouchesInView設置為NO時,在屏幕上滑動泳桦,兩種方式都在觸發(fā)汤徽,打印如下:
而當我們將pan.cancelsTouchesInView = YES
屬性設置為YES時,打印結果如下
我們發(fā)現(xiàn)touchesMoved的方法仍然被調用了灸撰,這是為什么呢谒府?這就涉及到第二個屬性delaysTouchesBegan
,這是因為手勢識別是有一個過程的浮毯,拖拽手勢需要一個很小的手指移動的過程才能被識別為拖拽手勢完疫,而在一個手勢觸發(fā)之前,是會一并發(fā)消息給事件傳遞鏈的债蓝,所以才會有最開始的幾個touchMoved方法被調用壳鹤,當識別出拖拽手勢以后,就會終止touch事件的傳遞惦蚊。
delaysTouchesBgan屬性用于控制這個消息的傳遞時機器虾,默認這個屬性為NO,此時在觸摸開始的時候蹦锋,就會發(fā)消息給事件傳遞鏈兆沙,如果我們設置為YES,在觸摸沒有被識別失敗前莉掂,都不會給事件傳遞鏈發(fā)送消息葛圃。
因此當我們設置pan.delaysTouchesBegan = YES;
時打印內容如下
因為此時在拖拽手勢識別失敗之前,都不會給時間傳遞鏈發(fā)送消息憎妙,所以就不會在調用touchesMoved觸發(fā)事件了
而delaysTouchesEnded
屬性默認是YES库正,當設為YES時在手勢識別結束后,會等待一個很短的時間厘唾,如果沒有接收到新的手勢識別任務褥符,才會發(fā)送touchesEnded消息到事件傳遞鏈,設置為NO之后會立刻發(fā)送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<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"touchBegan手勢開始");
}
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"touchEnd手勢觸發(fā)結束");
}
-(void)tap:(UITapGestureRecognizer *)tap
{
NSLog(@"tap手勢觸發(fā)");
}
當tap.delaysTouchesEnded = NO;
時抚垃,輕拍三下屏幕喷楣,打印如下
我們發(fā)現(xiàn)我們每點擊一下趟大,都會立即發(fā)送touchesEnded消息到事件傳遞鏈。
而當
tap.delaysTouchesEnded = YES;
時铣焊,輕拍三下屏幕逊朽,打印如下等三下輕拍手勢識別結束后,才會發(fā)送消息到事件傳遞鏈曲伊。
3.2 重點方法詳解-手勢間的互斥處理
同一個View上是可以添加多個手勢對象的叽讳,默認這些手勢是互斥的,一個手勢觸發(fā)了就會默認屏蔽其他相似的手勢動作坟募。比如岛蚤,單擊和雙擊并存時,如果不做處理婿屹,它就只能發(fā)送出單擊的消息灭美。為了能夠識別出雙擊手勢推溃,就需要用下面的方法一個特殊處理邏輯昂利,即先判斷手勢是否是雙擊,在雙擊失效的情況下作為單擊手勢處理铁坎。
- (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer;
[A requireGestureRecognizerToFail:B] 它可以指定當A手勢發(fā)生時蜂奸,即便A已經(jīng)滿足條件了,也不會立刻觸發(fā)硬萍,會等到指定的手勢B確定失敗之后才觸發(fā)扩所。
看一個例子
- (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手勢觸發(fā)失敗時才會觸發(fā)tap1手勢
[tap1 requireGestureRecognizerToFail:tap2];
}
-(void)tap1:(UITapGestureRecognizer *)tap
{
NSLog(@"tap1手勢觸發(fā)");
}
-(void)tap2:(UITapGestureRecognizer *)tap
{
NSLog(@"tap2手勢觸發(fā)");
}
3.3. UIGestureRecognizerDelegate
前面我們提到過關于手勢對象的協(xié)議代理,通過代理的回調朴乖,我們可以進行自定義手勢祖屏,也可以處理一些復雜的手勢關系,其中方法如下:
//手指觸摸屏幕后回調的方法买羞,返回NO則不再進行手勢識別袁勺,方法觸發(fā)等
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;
//開始進行手勢識別時調用的方法,返回NO則結束畜普,不再觸發(fā)手勢
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;
//是否支持多時候觸發(fā)期丰,返回YES,則可以多個手勢一起觸發(fā)方法吃挑,返回NO則為互斥
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
//下面這個兩個方法也是用來控制手勢的互斥執(zhí)行的
//這個方法返回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
點擊手勢十分簡單端辱,支持單擊和多次點擊蟹瘾,在我們手指觸摸屏幕并抬起手指時會進行觸發(fā),其中有如下兩個屬性我們可以進行設置:
//設置點擊次數(shù)掠手,默認為單擊
@property (nonatomic) NSUInteger numberOfTapsRequired;
//設置同時點擊的手指數(shù)
@property (nonatomic) NSUInteger numberOfTouchesRequired;
2. 捏合手勢——UIPinchGestureRecognizer
捏合手勢是當我們雙指捏合和擴張會觸發(fā)動作的手勢憾朴,我們可以設置的屬性如下:
//設置縮放比例
@property (nonatomic) CGFloat scale;
//設置捏合速度
@property (nonatomic,readonly) CGFloat velocity;
3. 拖拽手勢——UIPanGestureRecognzer
當我們點中視圖進行慢速拖拽時會觸發(fā)拖拽手勢的方法。
//設置觸發(fā)拖拽的最少觸摸點喷鸽,默認為1
@property (nonatomic) NSUInteger minimumNumberOfTouches;
//設置觸發(fā)拖拽的最多觸摸點
@property (nonatomic) NSUInteger maximumNumberOfTouches;
//獲取當前位置
- (CGPoint)translationInView:(nullable UIView *)view;
//設置當前位置
- (void)setTranslation:(CGPoint)translation inView:(nullable UIView *)view;
//設置拖拽速度
- (CGPoint)velocityInView:(nullable UIView *)view;
4. 滑動手勢——UISwipeGestureRecognizer
滑動手勢和拖拽手勢的不同之處在于滑動手勢更快众雷,而拖拽比較慢。
//設置觸發(fā)滑動手勢的觸摸點數(shù)
@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
進行旋轉動作時觸發(fā)手勢方法做祝。
//設置旋轉角度
@property (nonatomic) CGFloat rotation;
//設置旋轉速度
@property (nonatomic,readonly) CGFloat velocity;
6. 長按手勢——UILongPressGestureRecognizer
進行長按的時候觸發(fā)的手勢方法砾省。
//設置觸發(fā)前的點擊次數(shù)
@property (nonatomic) NSUInteger numberOfTapsRequired;
//設置觸發(fā)的觸摸點數(shù)
@property (nonatomic) NSUInteger numberOfTouchesRequired;
//設置最短的長按時間
@property (nonatomic) CFTimeInterval minimumPressDuration;
//設置在按觸時時允許移動的最大距離 默認為10像素
@property (nonatomic) CGFloat allowableMovement;
7. 自定義手勢
自定義手勢繼承:UIGestureRecognizer,實現(xiàn)下面的方法混槐,在以下方法中判斷自定義手勢是否實現(xiàn)编兄。
– touchesBegan:withEvent:
– touchesMoved:withEvent:
– touchesEnded:withEvent:
- touchesCancelled:withEvent:
注意.m文件中需要引入#import <UIKit/UIGestureRecognizerSubclass.h>。
關于iOS-UITouch事件處理過程可以看這篇文章iOS-UITouch事件處理詳解
?本文借鑒了很多前輩的文章声登,如果有不對的地方請指正狠鸳,歡迎大家一起交流學習 xx_cc 。