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); 

还有别的方法去监听touch或gesture事件,可参考https://developer.apple.com/library/ios/#documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html#//apple_ref/doc/uid/TP40006511-SW1

值得一提的是,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了,其中还涉及一个非公开的类,比较麻烦。

上一篇:ViewPager实现Recycle机制和响应notifyDataSetChanged


下一篇:《构建高可用Linux服务器 第3版》—— 1.4 Linux服务器的日志管理