概述
手勢識別器是處理視圖中的觸摸或者按壓事件最簡單的方法,我們可以在任意視圖上附加一個或多個手勢識別器。手勢識別器封裝了處理和解釋該視圖的觸摸事件所需的所有邏輯,并將其與已知模式進行匹配。當觸摸事件與已知模式匹配時,手勢識別器會通知其關聯(lián)的目標對象進行處理。手勢識別器使用target-action設計模式去發(fā)送通知,如下圖所示。當UITapGestureRecognizer對象在視圖中檢測到單指輕敲時,會調用視圖所屬的視圖控制器的操作方法來回應。

手勢識別器有兩種類型:離散型和連續(xù)型。當識別出手勢后,離散型手勢識別器只會調用一次其關聯(lián)目標對象的操作方法。連續(xù)型手勢識別器則可能會多次調用其關聯(lián)目標對象的操作方法,包括手勢的開始和結束以及跟蹤手勢細節(jié)的變化。例如,使用拖拽手勢識別器時,只要觸摸事件的位置發(fā)生變化,UIPanGestureRecognizer對象就會調用其關聯(lián)對象的操作方法。
配置手勢識別器
配置手勢識別器分為以下三步:
- 使用代碼創(chuàng)建一個手勢識別器對象,并將其附加到視圖上?;蛘?,使用
storyboard,直接將手勢識別器拖動到視圖上。 - 實現(xiàn)識別手勢后目標對象要調用的操作方法。
- 調用手勢識別器對象的
addTarget:action:方法將目標對象和目標對象要調用的操作方法與手勢識別器關聯(lián)起來?;蛘?,使用storyboard,右鍵單擊手勢識別器將其Sent Action selector連接到目標對象來創(chuàng)建關聯(lián)操作方法。
回應手勢
與手勢識別器關聯(lián)的操作方法為應用程序提供了對該手勢的響應。對于離散型手勢,其關聯(lián)的操作方法與按鈕的操作方法類似。只有手勢識別成功后,才會調用其關聯(lián)對象的操作方法作出響應。對于連續(xù)型手勢,其關聯(lián)的操作方法不僅可以對手勢識別成功后作出響應,還可以在識別手勢之前跟蹤手勢細節(jié)的變化。
手勢識別器的state屬性可以反映當前的手勢識別狀態(tài)。對于連續(xù)型手勢,手勢識別器會在手勢識別過程中將此屬性值從UIGestureRecognizerStateBegan變更為UIGestureRecognizerStateChanged,手勢被識別后變更為UIGestureRecognizerStateEnded。
點擊手勢
點擊手勢識別器UITapGestureRecognizer對象會簡要地檢測一個或多個手指點擊屏幕。在點擊手勢被識別成功前,涉及手勢的手指不能從初始觸摸點顯著移動到其他地方,但可以配置手指必須觸摸屏幕的次數(shù)。例如,可以配置點擊手勢識別器來檢測單擊,雙擊或者三擊。

點擊手勢屬于離散型手勢,只有當點擊手勢識別成功后才會調用其關聯(lián)對象的操作方法。由于手勢可以被取消的原因有很多,所以在操作方法中回應手勢前,檢查state屬性可以確保不會出錯。
如果觸摸屏幕后沒有調用與手勢識別器關聯(lián)的對象的操作方法,請檢查下列條件是否成立:
- 視圖的
userInteractionEnabled屬性設置為YES。UIImageView和UILabel類默認將此屬性設為NO。 - 點擊次數(shù)等于
numberOfTapsRequired屬性中指定的次數(shù)。 - 手指個數(shù)等于
numberOfTouchesRequired屬性中指定的個數(shù)。
長按手勢
長按手勢識別器UILongPressGestureRecognizer對象檢測一個或者多個手指長時間觸摸屏幕。在長按手勢被識別成功前,涉及手勢的手指不能從初始觸摸點顯著移動到其他地方,但可以配置手指必須觸摸屏幕的次數(shù)以及長按手勢的最短持續(xù)時間。手勢識別器僅由觸摸的持續(xù)時間而不是與其相關的力觸發(fā)。

長按手勢根據(jù)觸摸的持續(xù)時間來確定手勢的成功或者失敗,它屬于連續(xù)型手勢,手勢關聯(lián)對象的操作方法可能會隨著手勢狀態(tài)的變化而被多次調用。長按手勢識別器在用戶手指停留在屏幕上一定時間后(用戶手指仍舊停留在屏幕上)進入UIGestureRecognizerStateBegan狀態(tài),當觸摸事件更新時,進入UIGestureRecognizerStateChanged狀態(tài),用戶手指離開屏幕時,進入UIGestureRecognizerStateEnded狀態(tài)。
如果觸摸屏幕后沒有調用與手勢識別器關聯(lián)的對象的操作方法,請檢查下列條件是否成立:
- 視圖的
userInteractionEnabled屬性設置為YES。UIImageView和UILabel類默認將此屬性設為NO。 - 點擊次數(shù)等于
numberOfTapsRequired屬性中指定的次數(shù)。 - 手指個數(shù)等于
numberOfTouchesRequired屬性中指定的個數(shù)。 - 觸摸持續(xù)時間大于
minimumPressDuration屬性中指定的時間。
拖拽手勢
拖拽手勢識別器UIPanGestureRecognizer對象檢測一個或者多個手指在屏幕上移動。屏幕邊緣拖拽手勢是限定觸摸位置在屏幕邊緣的拖拽手勢,使用UIScreenEdgePanGestureRecognizer對象來識別屏幕邊緣拖拽手勢。

拖拽手勢屬于連續(xù)型手勢,在手勢識別過程中,會多次調用手勢關聯(lián)對象的操作方法。當手指開始移動時,拖拽手勢識別器進入UIGestureRecognizerStateBegan狀態(tài),繼續(xù)移動會導致手勢識別器進入UIGestureRecognizerStateChanged狀態(tài)。當手指離開屏幕時,進入UIGestureRecognizerStateEnded狀態(tài)。
使用UIPanGestureRecognizer對象的translationInView:方法可以獲取手指從初始觸摸位置移動的距離。在手勢開始時,拖拽手勢識別器會存儲初始觸摸點。如果手勢涉及多個手指,則手勢識別器會使用多個手指的觸摸點的中心點。
如果觸摸屏幕后沒有調用與手勢識別器關聯(lián)的對象的操作方法,請檢查下列條件是否成立:
- 視圖的
userInteractionEnabled屬性設置為YES。UIImageView和UILabel類默認將此屬性設為NO。 - 觸摸次數(shù)在
minimumNumberOfTouches和maximumNumberOfTouches屬性中指定的值之間。 - 如果是屏幕邊緣拖拽手勢,應確保觸摸位置在
edges屬性中指定的區(qū)域中。
輕掃手勢
輕掃手勢識別器UISwipeGestureRecognizer對象檢測屏幕上一個或多個手指在特定的水平或垂直方向上移動。輕掃手勢的方向和手指的數(shù)量是可以配置的,其屬于離散型手勢,只有在手勢被成功識別后才會調用手勢關聯(lián)對象的操作方法。當我們只關注手勢的結果而不關注手指的移動時,輕掃手勢是最合適的。

如果觸摸屏幕后沒有調用與手勢識別器關聯(lián)的對象的操作方法,請檢查下列條件是否成立:
- 視圖的
userInteractionEnabled屬性設置為YES。UIImageView和UILabel類默認將此屬性設為NO。 - 觸摸次數(shù)等于
numberOfTouchesRequired屬性中指定的值之間。 - 滑動的方向與
direction屬性值相匹配。
捏合手勢
捏合手勢屬于連續(xù)型手勢,其跟蹤最先觸摸屏幕的兩根手指之間的距離,使用UIPinchGestureRecognizer對象來檢測捏合手勢。

當兩個手指間的距離開始改變時,會更新捏合手勢識別器對象的手指間當前距離與初始距離的比例scale屬性值,然后調用手勢關聯(lián)目標對象的操作方法。捏合手勢常用于更改屏幕上的對象或者內容的大小。在縮放內容大小時,應該取scale值和內容初始大小的積。
如果觸摸屏幕后沒有調用與手勢識別器關聯(lián)的對象的操作方法,請檢查下列條件是否成立:
- 視圖的
userInteractionEnabled屬性設置為YES。UIImageView和UILabel類默認將此屬性設為NO。 - 至少兩根手指同時觸摸屏幕。
- 正在使用
scale值縮放內容大小。
旋轉手勢
旋轉手勢屬于連續(xù)型手勢,其跟蹤觸摸屏幕的兩根手指旋轉的角度,使用UIRotationGestureRecognizer對象來檢測旋轉手勢。

當手指開始在屏幕上旋轉時,會更新旋轉手勢識別器對象的從初始到現(xiàn)在已旋轉角度rotation屬性值,然后調用手勢關聯(lián)目標對象的操作方法??梢允褂眯D手勢來旋轉視圖或者更新自定義控件的值。
如果觸摸屏幕后沒有調用與手勢識別器關聯(lián)的對象的操作方法,請檢查下列條件是否成立:
- 視圖的
userInteractionEnabled屬性設置為YES。UIImageView和UILabel類默認將此屬性設為NO。 - 至少兩根手指同時觸摸屏幕。
- 正在使用
rotation旋轉內容。
處理手勢沖突
在視圖上附加多個手勢識別器時,多個手勢識別器會同時跟蹤傳入的觸摸事件。但默認情況下UIKit只允許在單個視圖中一次只識別一個手勢,也就是說當某個手勢識別器成功識別手勢后,其他手勢識別器就不會再繼續(xù)去識別該手勢。一次只識別一個手勢可以防止用戶一次觸發(fā)多個動作,但這種默認識別行為可能會導致意想不到的副作用。例如,在包含拖拽和輕掃手勢識別器的視圖中,輕掃手勢永遠不會被識別。這是因為拖拽手勢是連續(xù)的,所以它總是在輕掃手勢之前被識別。
UIKit通過調用手勢識別器的委托對象的代理方法來確定一個手勢是否必須在其他手勢之前或之后被識別,或者兩個手勢是否可以被同時識別。對于涉及潛在沖突的兩個手勢識別器,只需要其中一個手勢識別器去關聯(lián)委托對象,該委托對象需要遵循UIGestureRecognizerDelegate協(xié)議。
確定在視圖中識別手勢的順序
當視圖上附加有單擊和雙擊手勢時,單擊手勢會始終在雙擊手勢之前被識別,但是可以通過實現(xiàn)單擊手勢委托對象的gestureRecognizer:shouldRequireFailureOfGestureRecognizer:方法去延后單擊手勢的識別直到雙擊手勢被識別失敗,代碼如下:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if (gestureRecognizer == self.singleTap && otherGestureRecognizer == self.doubleTap)
{
return YES;
}
return NO;
}
當視圖上附加有輕掃和拖拽手勢時,拖拽手勢會始終在輕掃手勢之前被識別。在這種情況下,可以通過實現(xiàn)輕掃手勢委托對象的gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:方法實現(xiàn)在識別輕掃手勢失敗后才識別拖拽手勢,代碼如下:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if (gestureRecognizer == self.swip && otherGestureRecognizer == self.pan)
{
return YES;
}
return NO;
}
也可以通過實現(xiàn)拖拽手勢委托對象的gestureRecognizer:shouldRequireFailureOfGestureRecognizer:方法去延后拖拽手勢的識別直到輕掃手勢被識別失敗,代碼如下:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if (gestureRecognizer == self.pan && otherGestureRecognizer == self.swip)
{
return YES;
}
return NO;
}
允許同時識別多個手勢
在默認情況下,UIkit在單個視圖中一次只允許識別一個手勢,但如果有需要,也可以在單個視圖中同時識別多個手勢。要允許在單個視圖中同時識別多個手勢,需要手勢委托對象實現(xiàn)gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:方法。UIKit會調用此方法來判斷是否允許同時識別兩個手勢,默認返回NO。
當一個視圖上同時附加有拖拽、 縮放和旋轉手勢時,允許用戶在屏幕上可以同時拖動、 縮放和旋轉視圖,代碼如下:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
// 允許同時識別在同一視圖上的特定手勢
if (gestureRecognizer.view == self.targetView && otherGestureRecognizer.view == self.targetView)
{
// 排除長按手勢
if (![gestureRecognizer isMemberOfClass:[UILongPressGestureRecognizer class]] && ![otherGestureRecognizer isMemberOfClass:[UILongPressGestureRecognizer class]])
{
return YES;
}
}
return NO;
}
要同時識別多個手勢,它們的委托對象都要實現(xiàn)gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:方法,如果只有其中一個手勢的委托對象實現(xiàn)了,但其它手勢的委托對象沒有實現(xiàn),那么UIKit也不會同時識別它們。
自定義手勢識別器
當UIKit定義的手勢類型不能滿足我們需求時,我們也可以自定義手勢識別器來處理特定的觸摸事件。自定義手勢識別器的詳情可以參看Implementing a Custom Gesture Recognizer