手勢(shì)識(shí)別器(Gesture Recognizers)
手勢(shì)識(shí)別器將低級(jí)別的事件處理代碼轉(zhuǎn)換成高級(jí)別的動(dòng)作。它們是你綁定到視圖上的對(duì)象,這些對(duì)象允許視圖對(duì)動(dòng)作進(jìn)行響應(yīng),就像控件一樣。手勢(shì)識(shí)別器把觸摸解析成一個(gè)確定的手勢(shì),例如輕拂(swip),捏合(pinch),或者旋轉(zhuǎn)。如果它們識(shí)別出了被分配手勢(shì),會(huì)發(fā)送一條動(dòng)作消息給一個(gè)目標(biāo)對(duì)象。目標(biāo)對(duì)象很典型是視圖的視圖控制器,視圖控制器像圖1-1展示的那樣對(duì)手勢(shì)進(jìn)行響應(yīng)。這種設(shè)計(jì)模式簡(jiǎn)單而又強(qiáng)大;你能夠動(dòng)態(tài)的決定一個(gè)視圖要響應(yīng)哪個(gè)動(dòng)作,并且你能夠給一個(gè)視圖加上手勢(shì)識(shí)別器而不用創(chuàng)建視圖的子類(lèi)。

使用手勢(shì)識(shí)別器來(lái)簡(jiǎn)化事件處理
UIKit framework提供了能檢測(cè)到常見(jiàn)手勢(shì)的預(yù)定義手勢(shì)識(shí)別器。如果可能,使用預(yù)定義的手勢(shì)識(shí)別器是最好的方式,因?yàn)轭A(yù)定義手勢(shì)識(shí)別器的簡(jiǎn)單減少了你需要寫(xiě)的代碼數(shù)量。并且,使用一個(gè)標(biāo)準(zhǔn)的手勢(shì)識(shí)別器來(lái)代替你自己定義,能保證你的應(yīng)用的行為符合用戶(hù)的預(yù)期。
如果想讓你的應(yīng)用識(shí)別一個(gè)獨(dú)特的手勢(shì),比如打個(gè)對(duì)號(hào)或者一個(gè)旋轉(zhuǎn)手勢(shì),你可以創(chuàng)建自定義的手勢(shì)識(shí)別器。想要學(xué)習(xí)如何設(shè)計(jì)和完成你自己的手勢(shì)識(shí)別器,請(qǐng)參見(jiàn)Creating a Custom Gesture Recognizer。
內(nèi)建的手勢(shì)識(shí)別器識(shí)別常見(jiàn)的手勢(shì)
在你設(shè)計(jì)應(yīng)用的時(shí)候,你需要考慮你想識(shí)別哪些手勢(shì)。然后,對(duì)于每一個(gè)手勢(shì),你需要決定下表1-1中的預(yù)定義手勢(shì)識(shí)別器哪一個(gè)夠用。
手勢(shì) | UIKit 類(lèi) |
---|---|
Tapping (any number of taps) | UITapGestureRecognizer |
Pinching in and out (for zooming a view) | UIPinchGestureRecognizer |
Panning or dragging | UIPanGestureRecognizer |
Swiping (in any direction) | UISwipeGestureRecognizer |
Rotating (fingers moving in opposite directions) | UIRotationGestureRecognizer |
Long press (also known as “touch and hold”) | UILongPressGestureRecognizer |
你的應(yīng)用應(yīng)該只以用戶(hù)期望的方式對(duì)手勢(shì)進(jìn)行反饋。例如,一個(gè)pinch應(yīng)該放大縮小,一個(gè)點(diǎn)擊應(yīng)該選擇某樣?xùn)|西。For guidelines about how to properly use gestures, see Apps Respond to Gestures, Not Clicks.
手勢(shì)識(shí)別器與視圖綁定
每個(gè)手勢(shì)識(shí)別器都是和一個(gè)視圖聯(lián)系起來(lái)的。相比之下,一個(gè)的視圖能擁有多個(gè)視圖控制器,因?yàn)橐粋€(gè)獨(dú)立的視圖能響應(yīng)多個(gè)手勢(shì)識(shí)別器。如果你想要一個(gè)手勢(shì)識(shí)別器識(shí)別發(fā)生在一個(gè)特定視圖上的觸摸,你得把這個(gè)手勢(shì)識(shí)別器綁定到這個(gè)視圖上去。當(dāng)用戶(hù)觸摸到這個(gè)視圖時(shí),手勢(shì)識(shí)別器將早于視圖對(duì)象收到一條觸摸發(fā)生的消息。因此,這個(gè)手勢(shì)識(shí)別器能夠代表視圖對(duì)觸摸進(jìn)行響應(yīng)。
手勢(shì)觸發(fā)動(dòng)作消息
當(dāng)一個(gè)手勢(shì)識(shí)別器識(shí)別出了一個(gè)特殊手勢(shì),它將發(fā)送一條動(dòng)作消息給它的目標(biāo)。要?jiǎng)?chuàng)建一個(gè)手勢(shì)識(shí)別器,你得對(duì)它進(jìn)行初始化,設(shè)置一個(gè)目標(biāo)(target)和一個(gè)動(dòng)作(action)。
離散和連續(xù)的手勢(shì)
手勢(shì)不是離散的就是連續(xù)的。一個(gè)離散的手勢(shì),例如點(diǎn)擊(tap),發(fā)生一次。一個(gè)連續(xù)的手勢(shì),例如捏合(pinching),發(fā)生在一個(gè)時(shí)間段內(nèi)。對(duì)于離散的手勢(shì),一個(gè)手勢(shì)識(shí)別器發(fā)送給它的目標(biāo)一個(gè)獨(dú)立的動(dòng)作消息。而連續(xù)手勢(shì)的手勢(shì)識(shí)別器會(huì)持續(xù)發(fā)送給目標(biāo)動(dòng)作消息,直到觸摸序列停止,如圖1-2所示。

使用手勢(shì)識(shí)別器對(duì)事件響應(yīng)
為你的應(yīng)用添加一個(gè)內(nèi)建的手勢(shì)識(shí)別器需要做三件事:
- 創(chuàng)建并配置一個(gè)手勢(shì)識(shí)別器實(shí)例;
這一步包括指定一個(gè)目標(biāo),動(dòng)作,和手勢(shì)的一些特殊屬性(如點(diǎn)擊次數(shù)); - 把這個(gè)手勢(shì)識(shí)別器綁定到視圖上;
- 完成處理這個(gè)手勢(shì)的動(dòng)作方法(action method)。
使用界面構(gòu)建器(IB)添加手勢(shì)識(shí)別器
在Xcode的IB中,添加一個(gè)手勢(shì)識(shí)別器和添加一個(gè)任何一個(gè)對(duì)象到界面上方式相同——從對(duì)象庫(kù)中拖拽一個(gè)手勢(shì)識(shí)別器到一個(gè)視圖上。你做完這些以后,手勢(shì)識(shí)別器會(huì)自動(dòng)地綁定到這個(gè)視圖上。你可以檢查你的手勢(shì)識(shí)別器綁定到了哪個(gè)視圖,并且,如果必要的話你可以改變nib文件中的連接。
創(chuàng)建完手勢(shì)識(shí)別器對(duì)象之后,你需要?jiǎng)?chuàng)建并連接一個(gè)動(dòng)作方法。這個(gè)方法將在連接的手勢(shì)識(shí)別器識(shí)別出它的手勢(shì)時(shí)被調(diào)用。如果你需要在這個(gè)動(dòng)作方法之外引用這個(gè)手勢(shì)識(shí)別器,你應(yīng)該為這個(gè)手勢(shì)識(shí)別器再創(chuàng)建并連接一個(gè)接口。你的代碼應(yīng)和清單1-1很像。
Listing 1-1 Adding a gesture recognizer to your app with Interface Builder
@interface APLGestureRecognizerViewController ()
@property (nonatomic, strong) IBOutlet UITapGestureRecognizer *tapRecognizer;
@end
@implementation
- (IBAction)displayGestureForTapRecognizer:(UITapGestureRecognizer *)recognizer
// Will implement method later...
}
@end
使用代碼添加手勢(shì)識(shí)別器
你也可以通過(guò)alloc和init一個(gè)具體的UIGestureRecognizer
子類(lèi)(例如UIPinchGestureRecognizer
)的實(shí)例。當(dāng)你初始化手勢(shì)識(shí)別器的時(shí)候,得指定一個(gè)目標(biāo)對(duì)象和一個(gè)動(dòng)作選擇器(selector),像清單1-2那樣。目標(biāo)對(duì)象常常是視圖的視圖控制器。
如果通過(guò)代碼建立一個(gè)手勢(shì)識(shí)別器,你需要使用addGestureRecognizer:
方法把它綁定到視圖上。清單1-2創(chuàng)建了一個(gè)獨(dú)立的點(diǎn)擊手勢(shì)識(shí)別器,指定了需要識(shí)別的手勢(shì)是一次點(diǎn)擊,然后把手勢(shì)識(shí)別器對(duì)象綁定到了一個(gè)視圖上。典型的做法是,你在視圖控制器的viewDidLoad方法中創(chuàng)建手勢(shì)識(shí)別器,如清單1-2展示。
Listing 1-2 Creating a single tap gesture recognizer programmatically
- (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];
// Do any additional setup after loading the view, typically from a nib
}
對(duì)離散手勢(shì)的響應(yīng)
你在創(chuàng)建一個(gè)手勢(shì)識(shí)別器的時(shí)候會(huì)把識(shí)別器連接到一個(gè)動(dòng)作方法上。使用這個(gè)方法對(duì)你的手勢(shì)識(shí)別器進(jìn)行響應(yīng)。清單1-3提供一個(gè)對(duì)離散手勢(shì)進(jìn)行響應(yīng)的例子。當(dāng)用戶(hù)點(diǎn)擊手勢(shì)識(shí)別器綁定的視圖時(shí),視圖控制器顯示一張寫(xiě)有“Tap.”的圖片。showGestureForTapRecognizer:
方法會(huì)確定視圖上手勢(shì)的位置,這個(gè)位置信息來(lái)自識(shí)別器的locationInView
屬性,然后把圖片在這個(gè)位置上顯示出來(lái)。
Note:下面的三個(gè)代碼案例都來(lái)自于Simple Gesture Recognizers案例項(xiàng)目,你可以查看更多的上下文。
Listing 1-3 Handling a double tap gesture
- (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;
}];
}
每一個(gè)手勢(shì)識(shí)別器都有自己的屬性集合。例如,在清單1-4中,showGestureForSwipeRecognizer:
方法使用了swipe手勢(shì)識(shí)別器的方向?qū)傩詠?lái)確定用戶(hù)是往左滑還是往右滑。然后,它使用這個(gè)值來(lái)使圖片從滑動(dòng)方向逐漸消失掉。
Listing 1-4 Responding to a left or right swipe gesture
// Respond to a swipe gesture
- (IBAction)showGestureForSwipeRecognizer:(UISwipeGestureRecognizer *)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];
// If gesture is a left swipe, specify an end location
// to the left of the current location
if (recognizer.direction == UISwipeGestureRecognizerDirectionLeft) {
location.x -= 220.0;
} else {
location.x += 220.0;
}
// Animate the image view in the direction of the swipe as it fades out
[UIView animateWithDuration:0.5 animations:^{
self.imageView.alpha = 0.0;
self.imageView.center = location;
}];
}
對(duì)連續(xù)手勢(shì)的響應(yīng)
連續(xù)手勢(shì)允許應(yīng)用對(duì)正在發(fā)生的手勢(shì)進(jìn)行響應(yīng)。例如,當(dāng)用戶(hù)pinching的時(shí)候應(yīng)用界面可以縮放,或者允許在屏幕內(nèi)對(duì)對(duì)象進(jìn)行拖拽。
清單1-5展示了一個(gè)和用戶(hù)手勢(shì)相同角度的“旋轉(zhuǎn)”圖片,并且當(dāng)用戶(hù)停止旋轉(zhuǎn)時(shí),showGestureForRotationRecognizer:
方法將被持續(xù)地調(diào)用,直到手指抬起。
Listing 1-5 Responding to a rotation gesture
// Respond to a rotation gesture
- (IBAction)showGestureForRotationRecognizer:(UIRotationGestureRecognizer *)recognizer {
// Get the location of the gesture
CGPoint location = [recognizer locationInView:self.view];
// Set the rotation angle of the image view to
// match the rotation of the gesture
CGAffineTransform transform = CGAffineTransformMakeRotation([recognizer rotation]);
self.imageView.transform = transform;
// Display an image view at that location
[self drawImageForGestureRecognizer:recognizer atPoint:location];
// If the gesture has ended or is canceled, begin the animation
// back to horizontal and fade out
if (([recognizer state] == UIGestureRecognizerStateEnded) || ([recognizer state] == UIGestureRecognizerStateCancelled)) {
[UIView animateWithDuration:0.5 animations:^{
self.imageView.alpha = 0.0;
self.imageView.transform = CGAffineTransformIdentity;
}];
}
}
方法每一次被調(diào)用時(shí),圖片都drawImageForGestureRecognizer:
方法被設(shè)置成不透明的。當(dāng)手勢(shì)完成的時(shí)候,圖片被animateWithDuration:
方法設(shè)置為透明的。showGestureForRotationRecognizer:
方法通過(guò)檢查手勢(shì)識(shí)別器的狀態(tài)來(lái)確定手勢(shì)是不是完成了。這些狀態(tài)在一個(gè)有限狀態(tài)機(jī)中的手勢(shì)識(shí)別器中有更詳細(xì)的解釋。
定義手勢(shì)識(shí)別器如何交互
很多時(shí)候,當(dāng)你把手勢(shì)識(shí)別器加到你的應(yīng)用中的時(shí)候,你得很清楚你想讓你的識(shí)別器(或者觸摸事件代碼)如何互相交互。因此,你首先要懂一點(diǎn)手勢(shì)識(shí)別器的工作方式。
手勢(shì)識(shí)別器在有限狀態(tài)機(jī)中的運(yùn)作
手勢(shì)識(shí)別器以預(yù)定設(shè)定好的方式從一個(gè)狀態(tài)遷移到另一個(gè)狀態(tài)。處于各個(gè)狀態(tài)時(shí),它們能夠遷移到一個(gè)或多個(gè)可能的下一個(gè)狀態(tài),這都是基于確定的情況。狀態(tài)機(jī)的變化情況,取決于這個(gè)手勢(shì)識(shí)別器是離散的還是連續(xù)的,就像圖1-3表示的那樣。所有的手勢(shì)識(shí)別器開(kāi)始于“不確定”狀態(tài)(UIGestureRecognizerStatePossible)。它們分析收到的所有的多面觸摸序列,并且在分析過(guò)程中要么是識(shí)別出來(lái),要么識(shí)別手勢(shì)失敗。識(shí)別手勢(shì)失敗意思是手勢(shì)識(shí)別器遷移到“失敗”狀態(tài)(UIGestureRecognizerStateFailed)。

當(dāng)一個(gè)離散的手勢(shì)識(shí)別器識(shí)別出來(lái)了它的手勢(shì),這個(gè)手勢(shì)識(shí)別器會(huì)從“不確定”狀態(tài)遷移到“已識(shí)別”狀態(tài)(UIGestureRecognizerStateRecognized) 并且整個(gè)識(shí)別完成。
對(duì)于連續(xù)的手勢(shì),當(dāng)手勢(shì)識(shí)別器第一次識(shí)別出手勢(shì)時(shí),該識(shí)別器會(huì)從“不確定”狀態(tài)遷移到“開(kāi)始”狀態(tài)(UIGestureRecognizerStateRecognized)。然后,它會(huì)從“開(kāi)始”狀態(tài)遷移到“改變”狀態(tài)(UIGestureRecognizerStateChanged),并且在手勢(shì)發(fā)生時(shí)持續(xù)地從“改變”改變遷移到“改變”狀態(tài)。當(dāng)用戶(hù)的最后一個(gè)手指從視圖上舉起離開(kāi)的時(shí)候,手勢(shì)識(shí)別器會(huì)遷移到“結(jié)束”狀態(tài)(UIGestureRecognizerStateEnded)。這個(gè)手勢(shì)識(shí)別至此完成。注意,“結(jié)束”狀態(tài)其實(shí)是“已識(shí)別”狀態(tài)的同義詞。
如果一個(gè)連續(xù)手勢(shì)的識(shí)別器判斷確定當(dāng)前手勢(shì)不再符合期望的模式,那么它也可以從“改變”狀態(tài)遷移到“取消”狀態(tài)(UIGestureRecognizerStateCancelled)。
手勢(shì)識(shí)別器每次改變狀態(tài)時(shí),它會(huì)向它的目標(biāo)發(fā)送一條消息,除非它遷移到了“失敗”狀態(tài)或“取消”狀態(tài)。因此,一個(gè)離散的手勢(shì)識(shí)別器在它從“不確定”遷移到“已識(shí)別”狀態(tài)時(shí),只會(huì)發(fā)送一條動(dòng)作消息。一個(gè)連續(xù)的手勢(shì)識(shí)別器在它改變狀態(tài)時(shí),會(huì)發(fā)送很多條動(dòng)作消息。
當(dāng)一個(gè)手勢(shì)識(shí)別器遷移到了“已識(shí)別”(或“結(jié)束”)狀態(tài)時(shí),它將重置它的狀態(tài)到“不確定”。狀態(tài)往回遷移到“不確定”的動(dòng)作不會(huì)觸發(fā)動(dòng)作消息。
手勢(shì)識(shí)別器間的相互作用
一個(gè)視圖可以綁定多個(gè)手識(shí)別器。使用該視圖的gestureRecognizers
屬性來(lái)查看視圖綁定了哪些手勢(shì)識(shí)別器。你也可以動(dòng)態(tài)地改變視圖處理手勢(shì)的方式,可以添加(addGestureRecognizer:
)或去掉(removeGestureRecognizer:
)某個(gè)手勢(shì)識(shí)別器。
當(dāng)一個(gè)視圖綁定了多個(gè)的手勢(shì)識(shí)別器時(shí),你可能想要改變手勢(shì)識(shí)別器接收和分析觸摸事件的競(jìng)爭(zhēng)方式。默認(rèn)情況下,手勢(shì)識(shí)別器沒(méi)有一個(gè)設(shè)定的順序決定哪個(gè)識(shí)別器先接收到觸摸。由于這個(gè)緣故,每次觸摸,觸摸傳遞到各個(gè)手勢(shì)識(shí)別器的順序都不盡相同。你可以覆寫(xiě)這個(gè)默認(rèn)的行為:
- 規(guī)定一個(gè)手勢(shì)識(shí)別器應(yīng)在另一個(gè)之前對(duì)觸摸進(jìn)行分析。
- 允許兩個(gè)手勢(shì)識(shí)別器同時(shí)進(jìn)行運(yùn)作。
- 阻止一個(gè)手勢(shì)識(shí)別器分觸摸。
使用UIGestureRecognizer
類(lèi)方法,代理方法,以及子類(lèi)覆寫(xiě)的方法,來(lái)使這些行為生效。
Declare ing a Specific Order for Two Gesture Recognizers
給兩個(gè)手勢(shì)識(shí)別器聲明一個(gè)特殊的順序
Imagine that you want to recognize a swipe and a pan gesture, and you want these two gestures to trigger distinct actions. By default, when the user attempts to swipe, the gesture is interpreted as a pan. This is because a swiping gesture meets the necessary conditions to be interpreted as a pan (a continuous gesture) before it meets the necessary conditions to be interpreted as a swipe (a discrete gesture).
想象你想識(shí)別一個(gè)swipe或一個(gè)pan手勢(shì),
For your view to recognize both swipes and pans, you want the swipe gesture recognizer to analyze the touch event before the pan gesture recognizer does. If the swipe gesture recognizer determines that a touch is a swipe, the pan gesture recognizer never needs to analyze the touch. If the swipe gesture recognizer determines that the touch is not a swipe, it moves to the Failed state and the pan gesture recognizer should begin analyzing the touch event.
You indicate this type of relationship between two gesture recognizers by calling the requireGestureRecognizerToFail: method on the gesture recognizer that you want to delay, as in Listing 1-6. In this listing, both gesture recognizers are attached to the same view.