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