1. 程式人生 > >iOS私有API(三) UIWebView下的手勢識別器gestureRecognizer

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了,其中還涉及一個非公開的類,比較麻煩。