iOS-手勢UIGestureRecognier詳解

一. 手勢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ā)了


tap打印內容

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 = NO;

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

pan.cancelsTouchesInView = YES

我們發(fā)現(xiàn)touchesMoved的方法仍然被調用了灸撰,這是為什么呢谒府?這就涉及到第二個屬性delaysTouchesBegan這是因為手勢識別是有一個過程的浮毯,拖拽手勢需要一個很小的手指移動的過程才能被識別為拖拽手勢完疫,而在一個手勢觸發(fā)之前,是會一并發(fā)消息給事件傳遞鏈的债蓝,所以才會有最開始的幾個touchMoved方法被調用壳鹤,當識別出拖拽手勢以后,就會終止touch事件的傳遞惦蚊。
delaysTouchesBgan屬性用于控制這個消息的傳遞時機器虾,默認這個屬性為NO,此時在觸摸開始的時候蹦锋,就會發(fā)消息給事件傳遞鏈兆沙,如果我們設置為YES,在觸摸沒有被識別失敗前莉掂,都不會給事件傳遞鏈發(fā)送消息葛圃。
因此當我們設置pan.delaysTouchesBegan = YES;時打印內容如下

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;時抚垃,輕拍三下屏幕喷楣,打印如下

tap.delaysTouchesEnded = NO;

我們發(fā)現(xiàn)我們每點擊一下趟大,都會立即發(fā)送touchesEnded消息到事件傳遞鏈。
而當tap.delaysTouchesEnded = YES;時铣焊,輕拍三下屏幕逊朽,打印如下
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 。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末悯嗓,一起剝皮案震驚了整個濱河市件舵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌脯厨,老刑警劉巖铅祸,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異合武,居然都是意外死亡临梗,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門稼跳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盟庞,“玉大人,你說我怎么就攤上這事岂贩∶>” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵萎津,是天一觀的道長卸伞。 經(jīng)常有香客問我,道長锉屈,這世上最難降的妖魔是什么荤傲? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮颈渊,結果婚禮上遂黍,老公的妹妹穿的比我還像新娘终佛。我一直安慰自己,他們只是感情好雾家,可當我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布铃彰。 她就那樣靜靜地躺著,像睡著了一般芯咧。 火紅的嫁衣襯著肌膚如雪牙捉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天敬飒,我揣著相機與錄音邪铲,去河邊找鬼。 笑死无拗,一個胖子當著我的面吹牛带到,可吹牛的內容都是我干的。 我是一名探鬼主播英染,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼揽惹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了税迷?” 一聲冷哼從身側響起永丝,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎箭养,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哥牍,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡毕泌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了嗅辣。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撼泛。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖澡谭,靈堂內的尸體忽然破棺而出愿题,到底是詐尸還是另有隱情,我是刑警寧澤蛙奖,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布潘酗,位于F島的核電站,受9級特大地震影響雁仲,放射性物質發(fā)生泄漏仔夺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一攒砖、第九天 我趴在偏房一處隱蔽的房頂上張望缸兔。 院中可真熱鬧日裙,春花似錦、人聲如沸惰蜜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽抛猖。三九已至政钟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間樟结,已是汗流浹背养交。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瓢宦,地道東北人碎连。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像驮履,于是被迫代替她去往敵國和親鱼辙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內容