iOS私有API(三) UIWebView下的手勢識別器gestureRecognizer
首先,UIWebView本身之上並沒有手勢識別器(gesture recognizer,下面簡稱手勢),而是其子view有。
通過gdb或lldb,我們很容易看到UIWebView的subviews層級關係,下面是使用一個UIWebView開啟百度首頁時的情況:
(lldb) po [g_webView recursiveDescription] $0 = 0x0ab202e0 <UIWebView: 0x7577160; frame = (0 78; 768 926); autoresize = W+H; layer = <CALayer: 0x7577210>> | <_UIWebViewScrollView: 0xa95c230; frame = (0 0; 768 926); clipsToBounds = YES; autoresize = H; gestureRecognizers = <NSArray: 0xa95c910>; layer = <CALayer: 0xa95c440>; contentOffset: {0, 0}> | | <UIImageView: 0xa95ddf0; frame = (0 0; 10 10); transform = [-1, 0, -0, -1, 0, 0]; alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0xa95de50>> | | <UIImageView: 0xa95dd60; frame = (0 0; 10 10); transform = [0, 1, -1, 0, 0, 0]; alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0xa95ddc0>> | | <UIImageView: 0xa95dcd0; frame = (0 0; 10 10); transform = [0, -1, 1, 0, 0, 0]; alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0xa95dd30>> | | <UIImageView: 0xa95db00; frame = (0 0; 10 10); alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0xa95dca0>> | | <UIImageView: 0xa95da70; frame = (-4.5 4.5; 10 1); transform = [0, 1, -1, 0, 0, 0]; alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0xa95dad0>> | | <UIImageView: 0xa95d9e0; frame = (-4.5 4.5; 10 1); transform = [0, -1, 1, 0, 0, 0]; alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0xa95da40>> | | <UIImageView: 0xa95d950; frame = (0 0; 1 10); transform = [-1, 0, -0, -1, 0, 0]; alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0xa95d9b0>> | | <UIImageView: 0xa95d780; frame = (0 0; 1 10); alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0xa95d920>> | | <UIImageView: 0xa95d6f0; frame = (0 920; 768 6); alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0xa95d750>> | | <UIImageView: 0xa95d440; frame = (0 0; 768 6); transform = [-1, 0, -0, -1, 0, 0]; alpha = 0; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0xa95d6c0>> | | <UIWebBrowserView: 0x83f3800; frame = (0 0; 768 926); text = '搜尋設定|登入註冊 新聞網頁貼吧知道音樂圖片視訊...'; gestureRecognizers = <NSArray: 0xab6c2c0>; layer = <UIWebLayer: 0x75781a0>> | | | <TileHostLayer: 0x7578870> (layer) | | | | <TileLayer: 0x7148440> (layer) | | | | <TileLayer: 0x714cd20> (layer) | | | | <TileLayer: 0x7144c00> (layer) | | | | <TileLayer: 0x71450e0> (layer) | | | <CALayer: 0xa97a0f0> (layer)
可知UIWebView之下主要是兩大view, _UIWebViewScrollView和UIWebBrowserView。_UIWebViewScrollView是繼承自UIScrollView的,所以它有著和UIScrollView一樣的手勢:
(lldb) po [0xa95c230 gestureRecognizers] $1 = 0x0ab228e0 <__NSArrayI 0xab228e0>( <UIScrollViewDelayedTouchesBeganGestureRecognizer: 0xa95c1c0; state = Possible; delaysTouchesBegan = YES; view = <_UIWebViewScrollView 0xa95c230>; target= <(action=delayed:, target=<_UIWebViewScrollView 0xa95c230>)>>, <UIScrollViewPanGestureRecognizer: 0xa95cc10; state = Possible; delaysTouchesEnded = NO; view = <_UIWebViewScrollView 0xa95c230>; target= <(action=handlePan:, target=<_UIWebViewScrollView 0xa95c230>)>> )
UIWebBrowserView則是一個很複雜的類,另外找個時間再詳細說吧。目前可知,它的繼承關係是:
UIWebBrowserView->UIWebDocumentView->UIWebTiledView->UIView
UIWebBrowserView下的手勢很多,開啟不同的網頁或者進行過一些操作後,手勢還會出現增減,即手勢是會動態變化的,因為其有幾個assistant(助手類,協作類),我會逐個介紹。
開啟百度首頁後不做任何操作,這時會有如下的手勢:
(lldb) po [0x83f3800 gestureRecognizers] $1 = 0x07193f50 <__NSArrayI 0x7193f50>( <UIWebTouchEventsGestureRecognizer: 0x7161ae0; state = Possible; view = <UIWebBrowserView 0x9b93200>> type: Unknown locationInWindow: (0.000000, 0.000000) locations: () identifiers: () phases: () scale: 0.000000 rotation: 0.000000, <UITapGestureRecognizer: 0x7161e60; state = Possible; view = <UIWebBrowserView 0x9b93200>; target= <(action=_singleTapRecognized:, target=<UIWebBrowserView 0x9b93200>)>; must-fail = { <UITapGestureRecognizer: 0x7162580; state = Possible; view = <UIWebBrowserView 0x9b93200>; target= <(action=_twoFingerDoubleTapRecognized:, target=<UIWebBrowserView 0x9b93200>)>; numberOfTapsRequired = 2; numberOfTouchesRequired = 2>, <UITapGestureRecognizer: 0x7161fb0; state = Possible; view = <UIWebBrowserView 0x9b93200>; target= <(action=_doubleTapRecognized:, target=<UIWebBrowserView 0x9b93200>)>; numberOfTapsRequired = 2> }>, <UITapGestureRecognizer: 0x7161fb0; state = Possible; view = <UIWebBrowserView 0x9b93200>; target= <(action=_doubleTapRecognized:, target=<UIWebBrowserView 0x9b93200>)>; numberOfTapsRequired = 2; must-fail-for = { <UITapGestureRecognizer: 0x7161e60; state = Possible; view = <UIWebBrowserView 0x9b93200>; target= <(action=_singleTapRecognized:, target=<UIWebBrowserView 0x9b93200>)>> }>, <UITapGestureRecognizer: 0x7162580; state = Possible; view = <UIWebBrowserView 0x9b93200>; target= <(action=_twoFingerDoubleTapRecognized:, target=<UIWebBrowserView 0x9b93200>)>; numberOfTapsRequired = 2; numberOfTouchesRequired = 2; must-fail-for = { <UITapGestureRecognizer: 0x7161e60; state = Possible; view = <UIWebBrowserView 0x9b93200>; target= <(action=_singleTapRecognized:, target=<UIWebBrowserView 0x9b93200>)>> }>, <UILongPressGestureRecognizer: 0x7162760; state = Possible; view = <UIWebBrowserView 0x9b93200>; target= <(action=_highlightLongPressRecognized:, target=<UIWebBrowserView 0x9b93200>)>>, <UILongPressGestureRecognizer: 0x7162880; state = Possible; view = <UIWebBrowserView 0x9b93200>; target= <(action=_longPressRecognized:, target=<UIWebBrowserView 0x9b93200>)>>, <UIPanGestureRecognizer: 0x7162a60; state = Possible; view = <UIWebBrowserView 0x9b93200>; target= <(action=_twoFingerPanRecognized:, target=<UIWebBrowserView 0x9b93200>)>>, <UITapAndAHalfRecognizer: 0x7191b50; state = Possible; delaysTouchesEnded = NO; view = <UIWebBrowserView 0x9b93200>; target= <(action=makeWebSelection:, target=<UIWebSelectionAssistant 0x718ee00>)>>, <UILongPressGestureRecognizer: 0x7191bf0; state = Possible; delaysTouchesEnded = NO; view = <UIWebBrowserView 0x9b93200>; target= <(action=makeWebSelection:, target=<UIWebSelectionAssistant 0x718ee00>)>> )
1. UIWebTouchEventsGestureRecognizer
這個手勢是UIWebBrowserView管理的(其餘由UIWebDocumentView管理),主要用於識別web touch以及web gesture事件,即當網頁內的js有如下語句中的一條,這時手勢就會真正起作用:
element.addEventListener("touchstart", touchStart, false);
element.addEventListener("touchmove", touchMove, false);
element.addEventListener("touchend", touchEnd, false);
element.addEventListener("touchcancel", touchCancel, false);
element.addEventListener("gesturestart", gestureStart, false);
element.addEventListener("gesturechange", gestureChange, false);
element.addEventListener("gestureend", gestureEnd, false);
值得一提的是,UIWebTouchEventsGestureRecognizer是UIWebBrowserView上最高優先順序的手勢,它有特殊的邏輯,可不通過改變state的機制來呼叫它的delegate的函式。當js處理事件的函式裡有如此一段時, 其它手勢在本輪操作中都將不會觸發。
event.preventDefault();
當網頁在監聽gesture事件時,此手勢會計算出scale(縮放大小),rotation(旋轉角度)等資訊引數傳到對應的js function裡。
2. 三個UITapGestureRecognizer
這三個手勢分別是:單指單擊,單指雙擊,雙指雙擊。可通過lldb資訊中的action區分。
單指單擊即會觸發通常的click事件
單指雙擊是縮放網頁的手勢
雙指雙擊會把經放大的網頁縮小回適應視窗大小顯示的狀態,即scalesPageToFit。這個也許很多使用者還不知道呢。
3. 三個UILongPressGestureRecognizer
這三個手勢的作用分別是:高亮,普通長按(相當於pc上的單擊右鍵),內容選擇。可通過lldb資訊中的action區分。
高亮長按手勢設定的觸發最短時間為0.12秒,從其實現看,與單指單擊的功能類似。最開始我以為這樣的設計是為了彌補tap的超時,但後來通過hook確定,單擊手勢的手指按下和鬆開的最大間隔時間是1.5秒(雙擊是0.35秒),卓卓有餘。現在看來,應該是因為tap手勢不允許手指移動,但longPress可以,如此可對使用者的晃手指操作做容錯。
普通長按手勢用做觸發長按選單(類似pc上的右鍵選單)。觸發最短時間設定為0.75秒。通常只有在<a>標籤上長按才能觸發。
內容選擇手勢由UIWebSelectionAssistant類來管理,在不可以觸發長按選單的地方長按,即會觸發此手勢,進入網頁內容選取流程(出現放大鏡或藍色塊區)。 uc瀏覽器和qq瀏覽器把這個叫做“自由複製”。
4. UITapAndAHalfRecognizer
1.5次點選手勢,由UIWebSelectionAssistant類來管理,即單擊後立刻再手指按下但不再立刻鬆開。從其action知,它的功能與內容選擇手勢相同。
5. UIPanGestureRecognizer
雙指平移手勢。模擬滑鼠滾輪(wheel scroll)的操作。
6. 文字編輯狀態下的手勢
單擊百度的搜尋框進入文字編輯流程後,會多了7個手勢:從action的名字看,分別是:單指三擊,單指雙擊,單指單擊,1.5次點選,雙指長按選擇,單指點選(未弄清楚與前面那個單擊的區別),長按出現放大鏡。這7個手勢都由UITextInteractionAssistant類來管理。
<UITextTapRecognizer: 0x1016ee40; state = Possible; delaysTouchesEnded = NO; view = <UIWebBrowserView 0x10b44e00>; target= <(action=oneFingerTripleTap:, target=<UITextInteractionAssistant 0x1016ed20>)>; numberOfTapsRequired = 3>
<UITextTapRecognizer: 0x1016f2b0; state = Possible; delaysTouchesEnded = NO; view = <UIWebBrowserView 0x10b44e00>; target= <(action=oneFingerDoubleTap:, target=<UITextInteractionAssistant 0x1016ed20>)>; numberOfTapsRequired = 2>
<UITextTapRecognizer: 0x1016f3d0; state = Possible; delaysTouchesEnded = NO; view = <UIWebBrowserView 0x10b44e00>; target= <(action=twoFingerSingleTap:, target=<UITextInteractionAssistant 0x1016ed20>)>; numberOfTouchesRequired = 2>
<UITapAndAHalfRecognizer: 0x1016f4f0; state = Possible; view = <UIWebBrowserView 0x10b44e00>; target= <(action=tapAndAHalf:, target=<UITextInteractionAssistant 0x1016ed20>)>>
<UILongPressGestureRecognizer: 0x1016f5b0; state = Possible; delaysTouchesEnded = NO; view = <UIWebBrowserView 0x10b44e00>; target= <(action=twoFingerRangedSelectGesture:, target=<UITextInteractionAssistant 0x1016ed20>)>>
<UITextTapRecognizer: 0x1016f730; state = Possible; delaysTouchesEnded = NO; view = <UIWebBrowserView 0x10b44e00>; target= <(action=oneFingerTap:, target=<UITextInteractionAssistant 0x1016ed20>)>>
<UIVariableDelayLoupeGesture: 0x1016f840; state = Possible; delaysTouchesEnded = NO; view = <UIWebBrowserView 0x10b44e00>; target= <(action=loupeGesture:, target=<UITextInteractionAssistant 0x1016ed20>)>>
7. 視訊播放狀態下的手勢
例如播放優酷的視訊時,會多了7個手勢。這裡不再列舉了,請分別看其action。
<MPTapGestureRecognizer: 0x908f6d0; baseClass = UIGestureRecognizer; state = Possible; cancelsTouchesInView = NO; view = <MPSwipableView 0x906f830>; target= <(action=_tapGestureRecognized:, target=<MPSwipableView 0x906f830>)>; must-fail-for = {
<MPSwipeGestureRecognizer: 0x9060b40; baseClass = UIGestureRecognizer; state = Possible; cancelsTouchesInView = NO; delaysTouchesEnded = NO; view = <MPSwipableView 0x906f830>; target= <(action=_swipeGestureRecognized:, target=<MPSwipableView 0x906f830>)>>
}>
<MPSwipeGestureRecognizer: 0x9060b40; baseClass = UIGestureRecognizer; state = Possible; cancelsTouchesInView = NO; delaysTouchesEnded = NO; view = <MPSwipableView 0x906f830>; target= <(action=_swipeGestureRecognized:, target=<MPSwipableView 0x906f830>)>; must-fail = {
<MPTapGestureRecognizer: 0x908f6d0; baseClass = UIGestureRecognizer; state = Possible; cancelsTouchesInView = NO; view = <MPSwipableView 0x906f830>; target= <(action=_tapGestureRecognized:, target=<MPSwipableView 0x906f830>)>>
}>
<UIPinchGestureRecognizer: 0x90547d0; state = Possible; cancelsTouchesInView = NO; delaysTouchesEnded = NO; view = <MPSwipableView 0x906f830>; target= <(action=_pinchGestureRecognized:, target=<MPSwipableView 0x906f830>)>>
<MPActivityGestureRecognizer: 0x907c6e0; baseClass = UIGestureRecognizer; state = Possible; cancelsTouchesInView = NO; delaysTouchesEnded = NO; view = <MPSwipableView 0x906f830>; target= <(action=_activityGestureRecognized:, target=<MPSwipableView 0x906f830>)>>
<UITapGestureRecognizer: 0x907b400; state = Possible; cancelsTouchesInView = NO; delaysTouchesEnded = NO; view = <MPSwipableView 0x906f830>; target= <(action=_viewWasTapped:, target=<MPInlineVideoController 0x1ca1a880>)>; must-fail = {
<UITapGestureRecognizer: 0x904db20; state = Possible; enabled = NO; cancelsTouchesInView = NO; delaysTouchesEnded = NO; view = <MPSwipableView 0x906f830>; target= <(action=_viewWasTapped:, target=<MPInlineVideoController 0x1ca1a880>)>; numberOfTapsRequired = 2>
}>
<UITapGestureRecognizer: 0x904db20; state = Possible; enabled = NO; cancelsTouchesInView = NO; delaysTouchesEnded = NO; view = <MPSwipableView 0x906f830>; target= <(action=_viewWasTapped:, target=<MPInlineVideoController 0x1ca1a880>)>; numberOfTapsRequired = 2; must-fail-for = {
<UITapGestureRecognizer: 0x907b400; state = Possible; cancelsTouchesInView = NO; delaysTouchesEnded = NO; view = <MPSwipableView 0x906f830>; target= <(action=_viewWasTapped:, target=<MPInlineVideoController 0x1ca1a880>)>>
}>
<UIPinchGestureRecognizer: 0x90ce670; state = Possible; cancelsTouchesInView = NO; delaysTouchesEnded = NO; view = <MPSwipableView 0x906f830>; target= <(action=_viewWasPinched:, target=<MPInlineVideoController 0x1ca1a880>)>>
知道這些手勢的作用:
1. 為這些手勢觸發時新增額外的操作。例如希望雙擊網頁時,做個冒煙花的動畫,可以把雙擊手勢找出來,然後
[_doubleTapGestureRcognizer addTarget:self action:@selector(fireworks:)];
2. 替換原有的操作
[_doubleTapGestureRcognizer removeTarget:nil action:NULL];
[_doubleTapGestureRcognizer addTarget:self action:@selector(fireworks:)];
如何用程式碼找出這些手勢:
inline void showGestureRecognizers(UIView* view, bool recursively = true)
{
static int level = -1;
level++;
for (UIGestureRecognizer *r in view.gestureRecognizers)
{
NSLog(@"%@", r);
}
if (recursively)
{
for (UIView *v in view.subviews)
showGestureRecognizers(v);
}
level--;
}
如何識別這個手勢是上述提到的那麼多手勢中的哪個:
首先,如果能以類名就能區別了,那是最簡單了,即
[gesture isKindOfClass:[UITapGestureRecognizer class]]
對於UITapGestureRecognizer,你可以讀取一下numberOfTapsRequired或者numberOfTouchesRequired屬性再做區分,其它手勢也類似。
如果那個手勢不是公開的,可以:
[gesture isKindOfClass:NSClassFromString(@"UIWebViewScrollView")]
還識別不出?只好用KVC來獲取action了,其中還涉及一個非公開的類,比較麻煩。