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