用戶可以以觸摸屏幕或搖動設備等多種方式操作他們的iOS設備妻味。 iOS系統(tǒng)會對用戶操作設備的時動作做出解釋并把該信息傳遞給應用程序。應用程序?qū)τ脩舾鞣N自然的動作都能處理越全面欣福,用戶體驗也就越好责球。
iOS系統(tǒng)通過事件(Event)將用戶的操作傳遞給app,事件分為多種形式:多觸摸事件拓劝,運動事件以及控制多媒體(通過外部硬件控制)的事件雏逾。程序中可以通過內(nèi)置在UIKit里的常用手勢和一些定制化手勢來響應用戶的各種操作手勢。如果可能的話郑临,最好是使用這些內(nèi)置的手勢栖博,可以縮減了代碼總量。內(nèi)置在UIKit里標準控件的手勢例如UIButton 和UISlider牧抵, 分別可以對點擊和拖動手勢響應笛匙。如果想應用程序識別一個獨特的手勢侨把,比如一個漩渦狀運動,可以創(chuàng)建自定義手勢妹孙。 手勢識別器為復雜的事件處理邏輯提供了一個高層次的抽象秋柄,應用程序中實現(xiàn)觸摸事件處理(touch-event handling)應該考慮使用。
UIKit 內(nèi)置的手勢及對應class包括:
點擊Tapping (any number of taps)::UITapGestureRecognizer
向內(nèi)/外捏Pinching in and out (for zooming a view)::UIPinchGestureRecognizer
滑動/拖動Panning or dragging::UIPanGestureRecognizer
快速滑動Swiping (in any direction)::UISwipeGestureRecognizer
旋轉(zhuǎn)Rotating (fingers moving in opposite directions)::UIRotationGestureRecognizer
長按Long press (also known as “touch and hold”)::UILongPressGestureRecognizer
手勢可以分為離散手勢和連續(xù)手勢蠢正。 一個離散手勢骇笔,如一次tap或一次swipe,只發(fā)生一次嚣崭。一個連續(xù)手勢笨触,如捏合,發(fā)生時持續(xù)一段時間雹舀。 對于離散手勢芦劣,手勢識別就給目標發(fā)送一個操作消息。對于連續(xù)手勢说榆,手勢識別則一直發(fā)送操作消息給目標直到操作結(jié)束虚吟。
你需要做三件事來添加一個內(nèi)建手勢識別到應用程序:
創(chuàng)建并配置一個手勢識別實例。該步驟包括分配一個響應手勢的目標签财,操作串慰,以及手勢指定的各種屬性(比如 numberOfTapsRequired).
把手勢識別連接到一個視圖。
實現(xiàn)響應手勢的操作方法唱蒸。
對于離散型手勢:
- (void)viewDidLoad
{
[super viewDidLoad];
// Create and initialize a tap gesture
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:@selector(respondToTapGesture:)];
// Specify that the gesture must be a single tap
tapRecognizer.numberOfTapsRequired = 1;
// Add the tap gesture recognizer to the view
[self.view addGestureRecognizer:tapRecognizer];
}
// 手勢的響應方法
- (IBAction)showGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer
{
// Get the location of the gesture
CGPoint location = [recognizer locationInView:self.view];
// Display an image view at that location
[self drawImageForGestureRecognizer:recognizer atPoint:location];
// Animate the image view so that it fades out
[UIView animateWithDuration:0.5 animations:^{
self.imageView.alpha = 0.0;
}];
}```
###對于連續(xù)型手勢:
// Respond to a rotation gesture
-
(IBAction)showGestureForRotationRecognizer:(UIRotationGestureRecognizer *)recognizer
{
CGPoint location = [recognizer locationInView:self.view];// 隨著手勢旋轉(zhuǎn)圖片 CGAffineTransform transform = CGAffineTransformMakeRotation([recognizer rotation]); self.imageView.transform = transform; [self drawImageForGestureRecognizer:recognizer atPoint:location]; // 通過手勢狀態(tài)決定最終形式 if (([recognizer state] == UIGestureRecognizerStateEnded) || ([recognizer state] == UIGestureRecognizerStateCancelled)) { [UIView animateWithDuration:0.5 animations:^{ self.imageView.alpha = 0.0; self.imageView.transform = CGAffineTransformIdentity; }]; }
}
###手勢狀態(tài)
手勢識別在有限狀態(tài)機中的各種預定的狀態(tài)之間切換邦鲫。所有的手勢識別起點都是“可能的狀態(tài)UIGestureRecognizerStatePossible”
* 對于離散型手勢,手勢可能識別失敗UIGestureRecognizerStateFailed或被識別UIGestureRecognizerStateRecognized神汹,從而結(jié)束識別過程庆捺;
![](http://upload-images.jianshu.io/upload_images/3494096-142c1938d62b0fb1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/480)
* 對于連續(xù)性手勢,每次手勢識別改變狀態(tài)時慎冤,除非過渡到失敗或取消狀態(tài)疼燥,都給它的響應目標發(fā)送消息。當手勢第一次被識別時蚁堤,從可能狀態(tài)UIGestureRecognizerStatePossible轉(zhuǎn)到開始狀態(tài)UIGestureRecognizerStateBegan醉者,然后過渡到改變狀態(tài)UIGestureRecognizerStateChanged, 當手勢發(fā)生時又從改變狀態(tài)變?yōu)楦淖儬顟B(tài)。 當用戶的最后一個手指從視圖上抬起時披诗,手勢識別過渡到結(jié)束狀態(tài)UIGestureRecognizerStateEnded, 識別完成(結(jié)束狀態(tài)是識別完成狀態(tài)的一個別名)撬即。如果一個連續(xù)手勢不再符合某個手勢預期的模式時,識別將過渡到取消狀態(tài)UIGestureRecognizerStateCancelled呈队。
![](http://upload-images.jianshu.io/upload_images/3494096-9a80687726df5df8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/520)
###多個手勢的競爭處理
使用UIGestureRecognizer類方法剥槐,委托方法,以及其子類重寫的方法可以處理如兩個手勢誰先響應宪摧、是否同時粒竖、哪個手勢禁止響應某個動作等競爭問題颅崩。最常見的容易沖突的swipe和pan手勢,swipe手勢會首先被識別為pan手勢蕊苗,但是如何打破這個順序沿后,讓swipe首先被識別,如果確定不是swipe再識別pan朽砰?
- (void)viewDidLoad
{
[super viewDidLoad];
[self.panRecognizer requireGestureRecognizerToFail:self.swipeRecognizer];
}
requireGestureRecognizerToFail: 使pan手勢在等待swipe手勢識別過渡到Failed狀態(tài)期間尖滚,始終處于Possible狀態(tài)。如果swipe手勢識別失敗了瞧柔,pan手勢分析事件并變?yōu)橄聜€狀態(tài)漆弄。如果swipe手勢識別過渡到Recognized 或者 Began狀態(tài),pan手勢就變?yōu)镕ailed 狀態(tài)造锅。 關(guān)于狀態(tài)過渡的信息參考[“Gesture Recognizers Operate in a Finite State Machine.”](https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/GestureRecognizer_basics/GestureRecognizer_basics.html#//apple_ref/doc/uid/TP40009541-CH2-SW22)
UIGestureRecognizerDelegate協(xié)議提供了方法來阻止手勢識別分析觸摸撼唾。 可以使用協(xié)議中的
gestureRecognizer:shouldReceiveTouch:方法 或 gestureRecognizerShouldBegin: (如果視圖或視圖控制器不能成為手勢識別的代理,可以使用)
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if ([touch view] == self.customSubview)
{
// If it is, prevent all of the delegate's gesture recognizers from receiving the touch
return NO;
}
return YES;
}
兩個手勢識別不能同時識別它們的不同手勢备绽,通過代理方法可以讓兩個手勢識別同時識別一個手勢gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:
如果你想要控制兩個識別的交互為一個單向關(guān)系券坞,你可以重寫canPreventGestureRecognizer:方法并返回NO(默認為YES)。 比如肺素,如果你想用一個旋轉(zhuǎn)手勢阻止一個捏合手勢,但是又不想捏合手勢阻止一個旋轉(zhuǎn)手勢宇驾,可以用 rotationGestureRecognizer canPreventGestureRecognizer:pinchGestureRecognizer;
在iOS 6.0 以后版本中倍靡,默認控件操作方法阻止重復手勢識別的行為。如一個按鈕的默認操作是一個單擊课舍。如果你有一個單擊手勢識別綁定到一個按鈕的父視圖上塌西,然后用戶點擊該按鈕,最后按鈕的操作方法接收觸摸事件而不是定制添加的手勢識別筝尾。 還有:
* 單個手勢在 UISlider上的快速滑動捡需,輕掃方向跟slider平行;
* 單個手指的在 UISwitch 控件上的慢速拖動手勢,方向跟switch平行筹淫。
###觸摸UITouch 和 事件UIEvent
一個觸摸UITouch 是一個手指在屏幕上的存在或運動站辉。 一個手勢有一個或多個觸摸。
一個事件UIEvent包含一個多點觸摸序列的所有觸摸,包含了應用程序決定如何響應該事件的事件信息损姜。 一個多點觸摸序列以一個手指觸摸屏幕開始饰剥,以最后一個手指離開屏幕結(jié)束。 當一個手指移動時摧阅,iOS給事件UIEvent發(fā)送多個觸摸對象UITouch汰蓉。
每個觸摸對象只跟蹤一個手指,并且只在觸摸序列期間跟蹤棒卷。 在序列期間顾孽,UIKit跟蹤手指并更新觸摸對象的各種屬性祝钢。 這些屬性包括觸摸階段,它在視圖中的位置若厚,前一個位置拦英,以及時間戳。
觸摸階段表明一個觸摸何時開始盹沈,它是移動的還是靜止的龄章,以及它何時結(jié)束---當手指不再觸摸屏幕的時間。應用程序在任何觸摸的每個階段之間接收事件對象乞封。
![](http://upload-images.jianshu.io/upload_images/3494096-0ed90934e7b493fd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/800)
各個階段對應代理方法:
touchesBegan:withEvent: 當一個或多個手指觸摸屏幕時調(diào)用
touchesMoved:withEvent:當一個或多個手勢移動時調(diào)用
touchesEnded:withEvent:當一個或多個手指離開屏幕時調(diào)用
touchesCancelled:withEvent:當觸摸序列被系統(tǒng)事件取消時調(diào)用做裙,比如有一個來電。
注意: 這些方法跟手勢識別狀態(tài)沒有關(guān)聯(lián)
當一個觸摸發(fā)生時肃晚,觸摸對象從UIApplication對象傳遞到UIWindow對象锚贱。 然后,窗口首先把觸摸發(fā)送給觸摸發(fā)生的視圖上關(guān)聯(lián)的手勢識別器关串,如果識別出響應手勢將響應者變?yōu)橐晥D所在vc拧廊,而不再發(fā)給視圖。如果沒有識別出手勢晋修,才會發(fā)送給視圖對象自身吧碾。即下圖123的順序:
![](http://upload-images.jianshu.io/upload_images/3494096-a5637ceebb8d1521.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/520)
我們可以通過屬性delaysTouchesBegan等延遲將事件傳遞給視圖,而讓手勢識別先進行完墓卦。
當你通過子類化實現(xiàn)一個定制的手勢識別器時倦春,最重要的事情是正確地使用手勢識別器的state,在各個狀態(tài)下獲取手勢的情況來識別落剪。
- (void)reset; // 在Recognized/Ended, Canceled, 或者Failed狀態(tài)時復位
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;// 離散手勢無此狀態(tài)
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
###響應鏈
當iOS系統(tǒng)識別到一個事件睁本,它把事件傳遞給看起來最有可能處理的初始對象,如果初始對象不能處理該事件忠怖,iOS繼續(xù)把它傳遞給更多的對象呢堰,直到找到能夠處理該事件的對象。這些有順序的對象構(gòu)成一個響應鏈(responder chain) 凡泣。
![響應鏈](http://upload-images.jianshu.io/upload_images/3494096-87ede27a9574ebe3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/600)
響應鏈與事件的傳遞鏈是互逆的:
事件傳遞過程:
iOS設備的硬件檢測到用戶的觸摸事件 -> 產(chǎn)生UIEvent -> UIApplication -> UIWindow -> ......
![](http://upload-images.jianshu.io/upload_images/3494096-61b5a96cc1aab35b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/840)
iOS系統(tǒng)通過hittest尋找響應者的過程就是一個遍歷樹的子節(jié)點的過程枉疼,需要遍歷所有相關(guān)的vc和vc上面的所有view及其子view,直到最葉子節(jié)點的view響應问麸,如果沒有子view能響應往衷,就找到最葉的vc;如果沒有葉子vc可以響應严卖,就找到UIWindow席舍;否則找到UIApplication。