一.Gesture Recognizers
Gesture
Recognizers是在iOS3.2引入的,可以用來識別手勢、簡化定制視圖事件處理的對象。GestureRecognizers的基類為UIGestureRecognizer,這一個抽象基類,定義了實現底層手勢識別行為的編程接口。在UIKit框架中提供了6個具體的手勢識別類,用來識別常見的手勢。這6個手勢識別器類為:
UITapGestureRecognizer:用來識別點擊手勢,包括單擊,雙擊,甚至三擊等。
UIPinchGestureRecognizer:用來識別手指捏合手勢。
UIPanGestureRecognizer:用來識別拖動手勢。
UISwipeGestureRecognizer:用來識別Swipe手勢。
UIRotationGestureRecognizer:用來識別旋轉手勢。
UILongPressGestureRecognizer:用來識別長按手勢。
為了識別手勢,需要將Gesture Recognizers關聯到其檢測觸摸事件的view上,可以使用UIView的addGestureRecognizer:方法將手勢識別器綁定到視圖上。GestureRecognizers在觸摸事件處理流程中,處于觀察者的角色,其不是view層級結構的一部分,所以也不參與responderchain。在將觸摸事件發送給hit-test view之前,系統會先將觸摸事件發送到hit-testview上綁定的或hit-test view父視圖(superview)上綁定的GestureRecognizers上。其流程大概如下圖所示:
注:圖中view與Gesture Recognizer的關系是,GestureRecognizer關聯在view或view的superview(可能多級)上。
二.Gesture Recognizers與事件分發路徑的關系
Gesture
Recognizers可能會延遲將觸摸事件發送到hit-test
view上,默認情況下,當GestureRecognizers識別到手勢后,會向hit-test
view發送cancel消息,來取消之前發給hit-testview的事件。控制這個流程的是UIGestureRecognizer的三個屬性
cancelsTouchesInView?(默認為YES)
delaysTouchesBegan?(默認為NO)
delaysTouchesEnded?(默認為YES)
cancelsTouchesInView為YES,表示當GestureRecognizers識別到手勢后,會向hit-test
view發送touchesCancelled:withEvent:消息來取消hit-test
view對此觸摸序列的處理,這樣只有GestureRecognizers能響應此觸摸序列,hit-testview不再響應。如果為NO,則不發送touchesCancelled:withEvent:消息給hit-testview,這樣會使Gesture
Recognizers和hit-test view同時響應觸摸序列。
delaysTouchesBegan為NO,表示觸摸序列開始時,而手勢識別器還未識別出此手勢時,touch事件會同時發向hit-testview,這樣在手勢識別器還未識別出此手勢,hit-testview同時也可以收到同樣的觸摸事件。如果為YES,則在手勢識別器在識別手勢的過程中,不會有任何觸摸事件發送給hit-testview,如果手勢識別器最終識別到了手勢,則也不會發送任何消息(包括touchesCancelled:withEvent:)給hit-testview;若干手勢識別最終沒有識別到手勢,則所有的觸摸事件在發給hit-test
view處理。關于這個特性,可參考UIScrollView的delaysContentTouches屬性。這樣屬性也謹慎使用,使用不當會導致UI無響應。
delaysTouchesEnded,在文檔上的解釋是,當手勢識別器在識別手勢時,對于UITouchPhaseEnded階段的touch會延遲發送給hit-testview,在手勢識別成功后,發送給hit-test viewcancel消息,手勢識別失敗時,發送原來的end消息。其給出了了這樣的例子識別雙擊操作的UITapGestureRecognizer對象,其numberOfTapsRequired設為2,在用戶進行雙擊操作時,如果delaysTouchesEnded為NO,則hit-testview中的調用序列為
touchesBegan:withEvent:,
touchesEnded:withEvent:,
touchesBegan:withEvent:,
and touchesCancelled:withEvent:
如果delaysTouchesEnded為YES,則調用序列為:
touchesBegan:withEvent:,
touchesBegan:withEvent:,
touchesCancelled:withEvent:,
touchesCancelled:withEvent:
但我在實際測試時,并非如此,實際測試的結果是,如果delaysTouchesEnded為NO,則調用序列為:
touchesBegan:withEvent:,
touchesEnded:withEvent:,
TapGestureRecognizer 檢測到雙擊
如果delaysTouchesEnded為YES,則調用序列為:
touchesBegan:withEvent:,
touchesEnded:withEvent:,
TapGestureRecognizer 檢測到雙擊
touchesCancelled:withEvent:
這個問題還沒搞清楚!
三.多個Gesture Recognizer之間的關系
在一個view上可以綁定多個GestureRecognizer,在默認情況下,觸摸序列中的觸摸事件會以不確定的次序在各個gesturerecognizer中傳遞,直到事件最終發送給hit-test
view(如果中間沒被GestureRecognizer識別出并截獲的話)。多個GestureRecognizer之間的關系也可以根據需要定制,主要有下面幾種行為
1.使其中一個gesturerecognizer失敗的情況下,另一個gesture recognizer才能分析事件。
以同時識別單擊操作和雙擊操作為例,兩個gesturerecognizers分別用來識別單擊和雙擊,分別為singleTapGesture和doubleTapGesture。在默認情況下,當用戶進行單擊操作時,singleTapGesture會識別出一個單擊操作,doubleTapGesture也會識別出一個雙擊動作,但我們的意圖是,這僅僅是一個雙擊操作。在這種情況下我們可以使用UIGestureRecognizer的requireGestureRecognizerToFail:方法來使singleTapGesture在doubleTapGesture識別識別的時候才分析事件,如果doubleTapGesture識別出雙擊事件,則singleTapGesture不會有任何動作。
[singleTapGesture requireGestureRecognizerToFail:doubleTapGesture];
需要注意的是,在這種情況下,如果用戶進行單擊操作,需要一段延時(即doubleTapGesture識別失敗),singleTapGesture才會識別出單擊動作,進行單擊處理,這段時間很多,對實際使用幾乎沒有影響。
2.精確控制gesturerecognizer是否響應某個事件或事件序列.
在UIGestureRecognizerDelegate協議中有兩個可選方法可以控制gesturerecognizer是否需要識別某些事件
gestureRecognizerShouldBegin:
此方法在gesturerecognizer視圖轉出UIGestureRecognizerStatePossible狀態時調用,如果返回NO,則轉換到UIGestureRecognizerStateFailed;如果返回YES,則繼續識別觸摸序列.(默認情況下為YES)
gestureRecognizer:shouldReceiveTouch:
此方法在window對象在有觸摸事件發生時,調用gesturerecognizer的touchesBegan:withEvent:方法之前調用,如果返回NO,則gesturerecognizer不會看到此觸摸事件。(默認情況下為YES).
另外,在UIGestureRecognizer類中也有兩個可以重寫的方法來完成與Delegate方法中相同的功能
- (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer*)preventedGestureRecognizer;
- (BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer*)preventingGestureRecognizer;
3.允許多個手勢識別器共同識別
默認情況下,兩個gesturerecognizers不會同時識別它們的手勢,但是你可以實現UIGestureRecognizerDelegate協議中的
gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:方法對其進行控制。這個方法在這兩個gesturerecognizers中的任意一個將block另一個的觸摸事件時調用,如果返回YES,則兩個gesturerecognizers可同時識別,如果返回NO,則并不保證兩個gesturerecognizers必不能同時識別,因為另外一個gesturerecognizer的此方法可能返回YES。也就是說兩個gesturerecognizers的delegate方法只要任意一個返回YES,則這兩個就可以同時識別;只有兩個都返回NO的時候,才是互斥的。默認情況下是返回NO。
有這樣一個例子,如果要偵測在window上的所有觸摸事件,可以將gesturerecognizer關聯到window上,默認情況下如果手勢被window識別,則子視圖中的gesturerecognizer就失效了,而我們在window上的gesturerecognizer的目的只是監控所有事件,但并不處理這些事件,具體事件的處理還需要子視圖中的各個gesturerecognizer去處理,這樣我們可以實現window上綁定gesturerecognizer的delegate方法,使gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:返回YES即可。
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {? ? return YES;}
四.UIScrollView的類似行為
scroll view沒有滾動欄,當在scroll view上有觸摸行為時其要識別出觸摸行為的目的是scrollview本身還是其內容子視圖。定制scrollview如何處理這種情況,看查看UIScrollView類的下列屬性和方法。
– touchesShouldBegin:withEvent:inContentView:
– touchesShouldCancelInContentView:
canCancelContentTouches
delaysContentTouches
參考:
Event Handling Guide for iOS – Gesture Recognizers
UIGestureRecognizer Class Reference
UIGestureRecognizerDelegate Protocol Reference
Detecting all touches in an app
UIScrollView Class Reference
How to recognize swipe gesture in UIScrollView
UIGestureRecognizer blocks subview for handling touchevents
UIButton touch is delayed when in UIScrollView
Why is scrolling a UITableView much more responsive than scrollinga UIScrollView?
How to cancel touches exactly like UIScrollView?