一.Gesture Recognizers
Gesture Recognizers是在iOS3.2引入的,可以用來識別手勢、簡化定制視圖事件處理的對象。Gesture Recognizers的基類為UIGestureRecognizer,這一個抽象基類,定義了實現(xiàn)底層手勢識別行為的編程接口。在UIKit框架中提供了6個具體的手勢識別類,用來識別常見的手勢。這6個手勢識別器類為:
UITapGestureRecognizer:用來識別點擊手勢,包括單擊,雙擊,甚至三擊等。
UIPinchGestureRecognizer:用來識別手指捏合手勢。
UIPanGestureRecognizer:用來識別拖動手勢。
UISwipeGestureRecognizer:用來識別Swipe手勢。
UIRotationGestureRecognizer:用來識別旋轉(zhuǎn)手勢。
UILongPressGestureRecognizer:用來識別長按手勢。
為了識別手勢,需要將Gesture Recognizers關(guān)聯(lián)到其檢測觸摸事件的view上,可以使用UIView的addGestureRecognizer:方法將手勢識別器綁定到視圖上。Gesture Recognizers在觸摸事件處理流程中,處于觀察者的角色,其不是view層級結(jié)構(gòu)的一部分,所以也不參與responder chain。在將觸摸事件發(fā)送給hit-test view之前,系統(tǒng)會先將觸摸事件發(fā)送到hit-test view上綁定的或hit-test view父視圖(superview)上綁定的Gesture Recognizers上。其流程大概如下圖所示:
注:圖中view與Gesture Recognizer的關(guān)系是,Gesture Recognizer關(guān)聯(lián)在view或view的superview(可能多級)上。
二.Gesture Recognizers與事件分發(fā)路徑的關(guān)系
Gesture Recognizers可能會延遲將觸摸事件發(fā)送到hit-test view上,默認(rèn)情況下,當(dāng)Gesture Recognizers識別到手勢后,會向hit-test view發(fā)送cancel消息,來取消之前發(fā)給hit-test view的事件。控制這個流程的是UIGestureRecognizer的三個屬性
cancelsTouchesInView(默認(rèn)為YES)
delaysTouchesBegan(默認(rèn)為NO)
delaysTouchesEnded(默認(rèn)為YES)
cancelsTouchesInView為YES,表示當(dāng)Gesture Recognizers識別到手勢后,會向hit-test view發(fā)送 touchesCancelled:withEvent:消息來取消hit-test view對此觸摸序列的處理,這樣只有Gesture Recognizers能響應(yīng)此觸摸序列,hit-test view不再響應(yīng)。如果為NO,則不發(fā)送touchesCancelled:withEvent:消息給hit-test view,這樣會使Gesture Recognizers和hit-test view同時響應(yīng)觸摸序列。
delaysTouchesBegan為NO,表示觸摸序列開始時,而手勢識別器還未識別出此手勢時,touch事件會同時發(fā)向hit-test view,這樣在手勢識別器還未識別出此手勢,hit-test view同時也可以收到同樣的觸摸事件。如果為YES,則在手勢識別器在識別手勢的過程中,不會有任何觸摸事件發(fā)送給hit-test view,如果手勢識別器最終識別到了手勢,則也不會發(fā)送任何消息(包括touchesCancelled:withEvent:)給hit-test view;若干手勢識別最終沒有識別到手勢,則所有的觸摸事件在發(fā)給hit-test view處理。關(guān)于這個特性,可參考UIScrollView的delaysContentTouches屬性。這樣屬性也謹(jǐn)慎使用,使用不當(dāng)會導(dǎo)致UI無響應(yīng)。
delaysTouchesEnded,在文檔上的解釋是,當(dāng)手勢識別器在識別手勢時,對于UITouchPhaseEnded階段的touch會延遲發(fā)送給hit-test view,在手勢識別成功后,發(fā)送給hit-test view cancel消息,手勢識別失敗時,發(fā)送原來的end消息。其給出了了這樣的例子識別雙擊操作的UITapGestureRecognizer對象,其numberOfTapsRequired設(shè)為2,在用戶進(jìn)行雙擊操作時,如果delaysTouchesEnded為NO,則hit-test view中的調(diào)用序列為
touchesBegan:withEvent:,
touchesEnded:withEvent:,
touchesBegan:withEvent:,
and touchesCancelled:withEvent:
如果delaysTouchesEnded為YES,則調(diào)用序列為:
touchesBegan:withEvent:,
touchesBegan:withEvent:,
touchesCancelled:withEvent:,
touchesCancelled:withEvent:
但我在實際測試時,并非如此,實際測試的結(jié)果是,如果delaysTouchesEnded為NO,則調(diào)用序列為:
touchesBegan:withEvent:,
touchesEnded:withEvent:,
TapGestureRecognizer 檢測到雙擊
如果delaysTouchesEnded為YES,則調(diào)用序列為:
touchesBegan:withEvent:,
touchesEnded:withEvent:,
TapGestureRecognizer 檢測到雙擊
touchesCancelled:withEvent:
這個問題還沒搞清楚!
三.多個Gesture Recognizer之間的關(guān)系
在一個view上可以綁定多個Gesture Recognizer,在默認(rèn)情況下,觸摸序列中的觸摸事件會以不確定的次序在各個gesture
recognizer中傳遞,直到事件最終發(fā)送給hit-test view(如果中間沒被Gesture
Recognizer識別出并截獲的話)。多個Gesture Recognizer之間的關(guān)系也可以根據(jù)需要定制,主要有下面幾種行為
1.使其中一個gesture recognizer失敗的情況下,另一個gesture recognizer才能分析事件。
以同時識別單擊操作和雙擊操作為例,兩個gesture recognizers分別用來識別單擊和雙擊,分別為singleTapGesture和doubleTapGesture。在默認(rèn)情況下,當(dāng)用戶進(jìn)行單擊操作時,singleTapGesture會識別出一個單擊操作,doubleTapGesture也會識別出一個雙擊動作,但我們的意圖是,這僅僅是一個雙擊操作。在這種情況下我們可以使用UIGestureRecognizer的requireGestureRecognizerToFail:方法來使singleTapGesture在doubleTapGesture識別識別的時候才分析事件,如果doubleTapGesture識別出雙擊事件,則singleTapGesture不會有任何動作。
[singleTapGesture requireGestureRecognizerToFail:doubleTapGesture];
需要注意的是,在這種情況下,如果用戶進(jìn)行單擊操作,需要一段延時(即doubleTapGesture識別失敗),singleTapGesture才會識別出單擊動作,進(jìn)行單擊處理,這段時間很多,對實際使用幾乎沒有影響。
2.精確控制gesture recognizer是否響應(yīng)某個事件或事件序列.
在UIGestureRecognizerDelegate協(xié)議中有兩個可選方法可以控制gesture recognizer是否需要識別某些事件
此方法在gesture recognizer視圖轉(zhuǎn)出UIGestureRecognizerStatePossible狀態(tài)時調(diào)用,如果返回NO,則轉(zhuǎn)換到UIGestureRecognizerStateFailed;如果返回YES,則繼續(xù)識別觸摸序列.(默認(rèn)情況下為YES)
gestureRecognizer:shouldReceiveTouch:
此方法在window對象在有觸摸事件發(fā)生時,調(diào)用gesture recognizer的touchesBegan:withEvent:方法之前調(diào)用,如果返回NO,則gesture recognizer不會看到此觸摸事件。(默認(rèn)情況下為YES).
另外,在UIGestureRecognizer類中也有兩個可以重寫的方法來完成與Delegate方法中相同的功能
(BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer;
(BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer *)preventingGestureRecognizer;
默認(rèn)情況下,兩個gesture recognizers不會同時識別它們的手勢,但是你可以實現(xiàn)UIGestureRecognizerDelegate協(xié)議中的
gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:方法對其進(jìn)行控制。這個方法在這兩個gesture recognizers中的任意一個將block另一個的觸摸事件時調(diào)用,如果返回YES,則兩個gesture recognizers可同時識別,如果返回NO,則并不保證兩個gesture recognizers必不能同時識別,因為另外一個gesture recognizer的此方法可能返回YES。也就是說兩個gesture recognizers的delegate方法只要任意一個返回YES,則這兩個就可以同時識別;只有兩個都返回NO的時候,才是互斥的。默認(rèn)情況下是返回NO。
有這樣一個例子,如果要偵測在window上的所有觸摸事件,可以將gesture recognizer關(guān)聯(lián)到window上,默認(rèn)情況下如果手勢被window識別,則子視圖中的gesture recognizer就失效了,而我們在window上的gesture recognizer的目的只是監(jiān)控所有事件,但并不處理這些事件,具體事件的處理還需要子視圖中的各個gesture recognizer去處理,這樣我們可以實現(xiàn)window上綁定gesture recognizer的delegate方法,使gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:返回YES即可。
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
scroll view沒有滾動欄,當(dāng)在scroll view上有觸摸行為時其要識別出觸摸行為的目的是scroll view本身還是其內(nèi)容子視圖。定制scrollview如何處理這種情況,看查看UIScrollView類的下列屬性和方法。
touchesShouldBegin:withEvent:inContentView:
touchesShouldCancelInContentView:
IOS開發(fā)之手勢——UIGestureRecognizer 共存
在 iPhone 或 iPad 的開發(fā)中,除了用touchesBegan / touchesMoved / touchesEnded這組方法來控制使用者的手指觸控外,也可以用UIGestureRecognizer的衍生類別來進(jìn)行判斷。用UIGestureRecognizer的好處在于有現(xiàn)成的手勢,開發(fā)者不用自己計算手指移動軌跡。UIGestureRecognizer的衍生類別有以下幾種:
UITapGestureRecognizer
UIPinchGestureRecognizer
UIRotationGestureRecognizer
UISwipeGestureRecognizer
UIPanGestureRecognizer
UILongPressGestureRecognizer
//定義一個 recognizer, 并加到需要偵測該手勢的 UIView 元件上
- (void)viewDidLoad {
UISwipeGestureRecognizer* recognizer;
//handleSwipeFrom 是偵測到手勢,所要呼叫的方法
recognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:selfaction:@selector(handleSwipeFrom)];
//不同的 Recognizer 有不同的實體變數(shù)
//例如 SwipeGesture 可以指定方向
//而 TapGesture 則可以指定次數(shù)
recognizer.direction = UISwipeGestureRecognizerDirectionUp
[self.view addGestureRecognizer:recognizer];
[recognizer release];
}
- (void)handleSwipeFrom:(UISwipeGestureRecognizer*)recognizer {
//觸發(fā)手勢事件后,在這里作些事情
//底下是刪除手勢的方法
[self.view removeGestureRecognizer:recognizer];
}
問題來了。有些手勢其實是互相關(guān)聯(lián)的,例如 Tap 與 LongPress、Swipe與 Pan,或是 Tap 一次與Tap 兩次。當(dāng)一個 UIView 同時添加兩個相關(guān)聯(lián)的手勢時,到底我這一下手指頭按的要算是 Tap 還是 LongPress?如果照預(yù)設(shè)作法來看,只要「先滿足條件」的就會跳出并呼叫對應(yīng)方法,舉例來說,如果同時注冊了 Pan 和 Swipe,只要手指頭一移動就會觸發(fā) Pan 然后跳出,因而永遠(yuǎn)都不會發(fā)生 Swipe;單點與雙點的情形也是一樣,永遠(yuǎn)都只會觸發(fā)單點,不會有雙點。
那么這個問題有解嗎?答案是肯定的,UIGestureRecognizer有個方法叫做requireGestureRecognizerToFail,他可以指定某一個 recognizer,即便自己已經(jīng)滿足條件了,也不會立刻觸發(fā),會等到該指定的 recognizer 確定失敗之后才觸發(fā)。以同時支持單點與雙點的手勢為例,代碼如下:
- (void)viewDidLoad {
//單擊的 Recognizer
UITapGestureRecognizer* singleRecognizer;
singleRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:selfaction:@selector(handleSingleTapFrom)];
singleTapRecognizer.numberOfTapsRequired =1;//單擊
[self.view addGestureRecognizer:singleRecognizer];
//雙擊的 Recognizer
UITapGestureRecognizer*double;
doubleRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:selfaction:@selector(handleDoubleTapFrom)];
doubleTapRecognizer.numberOfTapsRequired =2;//雙擊
[self.view addGestureRecognizer:doubleRecognizer];
//關(guān)鍵在這一行,如果雙擊確定偵測失敗才會觸發(fā)單擊
[singleRecognizer requireGestureRecognizerToFail:doubleRecognizer];
[singleRecognizer release];
[doubleRecognizer release];
}