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

使用手勢識別器來簡化事件處理
UIKit framework提供了能檢測到常見手勢的預定義手勢識別器。如果可能,使用預定義的手勢識別器是最好的方式,因為預定義手勢識別器的簡單減少了你需要寫的代碼數量。并且,使用一個標準的手勢識別器來代替你自己定義,能保證你的應用的行為符合用戶的預期。
如果想讓你的應用識別一個獨特的手勢,比如打個對號或者一個旋轉手勢,你可以創建自定義的手勢識別器。想要學習如何設計和完成你自己的手勢識別器,請參見Creating a Custom Gesture Recognizer。
內建的手勢識別器識別常見的手勢
在你設計應用的時候,你需要考慮你想識別哪些手勢。然后,對于每一個手勢,你需要決定下表1-1中的預定義手勢識別器哪一個夠用。
手勢 | UIKit 類 |
---|---|
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 |
你的應用應該只以用戶期望的方式對手勢進行反饋。例如,一個pinch應該放大縮小,一個點擊應該選擇某樣東西。For guidelines about how to properly use gestures, see Apps Respond to Gestures, Not Clicks.
手勢識別器與視圖綁定
每個手勢識別器都是和一個視圖聯系起來的。相比之下,一個的視圖能擁有多個視圖控制器,因為一個獨立的視圖能響應多個手勢識別器。如果你想要一個手勢識別器識別發生在一個特定視圖上的觸摸,你得把這個手勢識別器綁定到這個視圖上去。當用戶觸摸到這個視圖時,手勢識別器將早于視圖對象收到一條觸摸發生的消息。因此,這個手勢識別器能夠代表視圖對觸摸進行響應。
手勢觸發動作消息
當一個手勢識別器識別出了一個特殊手勢,它將發送一條動作消息給它的目標。要創建一個手勢識別器,你得對它進行初始化,設置一個目標(target)和一個動作(action)。
離散和連續的手勢
手勢不是離散的就是連續的。一個離散的手勢,例如點擊(tap),發生一次。一個連續的手勢,例如捏合(pinching),發生在一個時間段內。對于離散的手勢,一個手勢識別器發送給它的目標一個獨立的動作消息。而連續手勢的手勢識別器會持續發送給目標動作消息,直到觸摸序列停止,如圖1-2所示。

使用手勢識別器對事件響應
為你的應用添加一個內建的手勢識別器需要做三件事:
- 創建并配置一個手勢識別器實例;
這一步包括指定一個目標,動作,和手勢的一些特殊屬性(如點擊次數); - 把這個手勢識別器綁定到視圖上;
- 完成處理這個手勢的動作方法(action method)。
使用界面構建器(IB)添加手勢識別器
在Xcode的IB中,添加一個手勢識別器和添加一個任何一個對象到界面上方式相同——從對象庫中拖拽一個手勢識別器到一個視圖上。你做完這些以后,手勢識別器會自動地綁定到這個視圖上。你可以檢查你的手勢識別器綁定到了哪個視圖,并且,如果必要的話你可以改變nib文件中的連接。
創建完手勢識別器對象之后,你需要創建并連接一個動作方法。這個方法將在連接的手勢識別器識別出它的手勢時被調用。如果你需要在這個動作方法之外引用這個手勢識別器,你應該為這個手勢識別器再創建并連接一個接口。你的代碼應和清單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
使用代碼添加手勢識別器
你也可以通過alloc和init一個具體的UIGestureRecognizer
子類(例如UIPinchGestureRecognizer
)的實例。當你初始化手勢識別器的時候,得指定一個目標對象和一個動作選擇器(selector),像清單1-2那樣。目標對象常常是視圖的視圖控制器。
如果通過代碼建立一個手勢識別器,你需要使用addGestureRecognizer:
方法把它綁定到視圖上。清單1-2創建了一個獨立的點擊手勢識別器,指定了需要識別的手勢是一次點擊,然后把手勢識別器對象綁定到了一個視圖上。典型的做法是,你在視圖控制器的viewDidLoad方法中創建手勢識別器,如清單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
}
對離散手勢的響應
你在創建一個手勢識別器的時候會把識別器連接到一個動作方法上。使用這個方法對你的手勢識別器進行響應。清單1-3提供一個對離散手勢進行響應的例子。當用戶點擊手勢識別器綁定的視圖時,視圖控制器顯示一張寫有“Tap.”的圖片。showGestureForTapRecognizer:
方法會確定視圖上手勢的位置,這個位置信息來自識別器的locationInView
屬性,然后把圖片在這個位置上顯示出來。
Note:下面的三個代碼案例都來自于Simple Gesture Recognizers案例項目,你可以查看更多的上下文。
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;
}];
}
每一個手勢識別器都有自己的屬性集合。例如,在清單1-4中,showGestureForSwipeRecognizer:
方法使用了swipe手勢識別器的方向屬性來確定用戶是往左滑還是往右滑。然后,它使用這個值來使圖片從滑動方向逐漸消失掉。
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;
}];
}
對連續手勢的響應
連續手勢允許應用對正在發生的手勢進行響應。例如,當用戶pinching的時候應用界面可以縮放,或者允許在屏幕內對對象進行拖拽。
清單1-5展示了一個和用戶手勢相同角度的“旋轉”圖片,并且當用戶停止旋轉時,showGestureForRotationRecognizer:
方法將被持續地調用,直到手指抬起。
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;
}];
}
}
方法每一次被調用時,圖片都drawImageForGestureRecognizer:
方法被設置成不透明的。當手勢完成的時候,圖片被animateWithDuration:
方法設置為透明的。showGestureForRotationRecognizer:
方法通過檢查手勢識別器的狀態來確定手勢是不是完成了。這些狀態在一個有限狀態機中的手勢識別器中有更詳細的解釋。
定義手勢識別器如何交互
很多時候,當你把手勢識別器加到你的應用中的時候,你得很清楚你想讓你的識別器(或者觸摸事件代碼)如何互相交互。因此,你首先要懂一點手勢識別器的工作方式。
手勢識別器在有限狀態機中的運作
手勢識別器以預定設定好的方式從一個狀態遷移到另一個狀態。處于各個狀態時,它們能夠遷移到一個或多個可能的下一個狀態,這都是基于確定的情況。狀態機的變化情況,取決于這個手勢識別器是離散的還是連續的,就像圖1-3表示的那樣。所有的手勢識別器開始于“不確定”狀態(UIGestureRecognizerStatePossible)。它們分析收到的所有的多面觸摸序列,并且在分析過程中要么是識別出來,要么識別手勢失敗。識別手勢失敗意思是手勢識別器遷移到“失敗”狀態(UIGestureRecognizerStateFailed)。

當一個離散的手勢識別器識別出來了它的手勢,這個手勢識別器會從“不確定”狀態遷移到“已識別”狀態(UIGestureRecognizerStateRecognized) 并且整個識別完成。
對于連續的手勢,當手勢識別器第一次識別出手勢時,該識別器會從“不確定”狀態遷移到“開始”狀態(UIGestureRecognizerStateRecognized)。然后,它會從“開始”狀態遷移到“改變”狀態(UIGestureRecognizerStateChanged),并且在手勢發生時持續地從“改變”改變遷移到“改變”狀態。當用戶的最后一個手指從視圖上舉起離開的時候,手勢識別器會遷移到“結束”狀態(UIGestureRecognizerStateEnded)。這個手勢識別至此完成。注意,“結束”狀態其實是“已識別”狀態的同義詞。
如果一個連續手勢的識別器判斷確定當前手勢不再符合期望的模式,那么它也可以從“改變”狀態遷移到“取消”狀態(UIGestureRecognizerStateCancelled)。
手勢識別器每次改變狀態時,它會向它的目標發送一條消息,除非它遷移到了“失敗”狀態或“取消”狀態。因此,一個離散的手勢識別器在它從“不確定”遷移到“已識別”狀態時,只會發送一條動作消息。一個連續的手勢識別器在它改變狀態時,會發送很多條動作消息。
當一個手勢識別器遷移到了“已識別”(或“結束”)狀態時,它將重置它的狀態到“不確定”。狀態往回遷移到“不確定”的動作不會觸發動作消息。
手勢識別器間的相互作用
一個視圖可以綁定多個手識別器。使用該視圖的gestureRecognizers
屬性來查看視圖綁定了哪些手勢識別器。你也可以動態地改變視圖處理手勢的方式,可以添加(addGestureRecognizer:
)或去掉(removeGestureRecognizer:
)某個手勢識別器。
當一個視圖綁定了多個的手勢識別器時,你可能想要改變手勢識別器接收和分析觸摸事件的競爭方式。默認情況下,手勢識別器沒有一個設定的順序決定哪個識別器先接收到觸摸。由于這個緣故,每次觸摸,觸摸傳遞到各個手勢識別器的順序都不盡相同。你可以覆寫這個默認的行為:
- 規定一個手勢識別器應在另一個之前對觸摸進行分析。
- 允許兩個手勢識別器同時進行運作。
- 阻止一個手勢識別器分觸摸。
使用UIGestureRecognizer
類方法,代理方法,以及子類覆寫的方法,來使這些行為生效。
Declare ing a Specific Order for Two Gesture Recognizers
給兩個手勢識別器聲明一個特殊的順序
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).
想象你想識別一個swipe或一個pan手勢,
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.