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

时间: 2024-10-28 09:28:54

iOS私有API(三) UIWebView下的手势识别器gestureRecognizer的相关文章

UIWebView下各种手势识别器的协作处理方案

 前置阅读: 1. iOS私有API(二) UIGestureRecognizerDelegate的两个函数 2. iOS私有API(三) UIWebView下的手势识别器gestureRecognizer UIWebView下有很多的手势,它是怎么管理的呢?主要是两种途径:自管理和委托,即 1. 继承自UIGestureRecognizer或其子类,重载以下两个函数 // same behavior as the equivalent delegate methods, but can be

iOS私有API(二) UIGestureRecognizerDelegate的两个函数

UIGestureRecognizerDelegate有两个没公开的函数,只要重载了就会被调用. 即所有的UIGestureRecognizer子类.delegate = someInstance; 经过set以后,只要这个delegate实例里有这两个函数,就会被调用进入.经过验证,这两个api是可以通过apple审查上app store的. - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer canBePrev

iOS私有API(一) -[UIApplication _cancelAllTouches]

 这个api会令当前的所有手指操作失效,即令所有的UITouch变成cancelled状态,所有的UIView和UIGestureRecognizer都会触发touchesCancelled:withEvent:事件. 使用场景: 1. 模拟UIPopoverController时的行为,当popover弹出时,其他手势都应该被cancelled掉,从而保证popover弹出后继续移动手指不会再产生事件.(UIPopoverController就有使用这个函数,UIActionSheet应该也有

iOS开发之私有API No.1 私有API的查找和生成

最近研究一个应用,可以在un-jailbreak的情况下打开4G功能,是的,就是很火的"移动流量仪",想要知道他是如何做到的,正好我的手机已经jailbreak了,打开SSH通道,然后用find -mmin 1 命令来查看1分钟内改动后的文件,发现是修改了系统的一个配置文件.于是就猜测应该是调用了私有的API.就顺着查下去,先从私有API说起吧: 一.关于苹果API: 1.Published API(公开的API):又称 Documented API(文档中记录的API).是苹果通过

iOS软件被曝使用私有API:你的数据被偷了

不同的配方,相同的味道.App Store 一直都在飞快地发展,现在这块蛋糕越来越大,尤其是在中国地区.所以,想要从这块蛋糕上尝到甜头的人也越来越多.做法正经的,我们叫他们商人;做法不正经的,我们只能称之为奸商甚至是犯罪份子了.可怜开发者,要背锅了. 罪恶之源被曝光 "我们已经了解到,一系列采用第三方移动广告 SDK 进行开发的 iOS 软件会通过私人 API 擅自收集用户的个人信息,包括邮箱地址.设备序列号以及路由数据等等.然后,这些软件把用户数据传向一家名为有米的移动广告供应商所设的服务器,

iOS 画面切换的各种动画效果附私有API

ios的画面切换的动画效果的API主要通过调用系统已定义的动画效果实现,这些效果已基本囊括开发的需求,如果需要更加复杂的效果,可以考虑CATransition来实现 以下是基本的四种效果 kCATransitionPush 推入效果 kCATransitionMoveIn 移入效果 kCATransitionReveal 截开效果 kCATransitionFade 渐入渐出效果 以下API效果可以安全使用 cube 方块 suckEffect 三角 rippleEffect 水波抖动 page

IOS打包验证时,提示使用了私有API的问题

提示有setCities等私有API,于是打包没通过, 这是由于 使用了百度地图SDK的缘故,只需要在other link flags中设置一个参数:-ObjC 这样就可以解决这个问题了.

谈谈iOS的Safari和UIWebView

打开iOS4.3上的iPad Safari,再跟iOS6.1上的对比,也许你会觉得4.3没有标签栏,不够好用,但那个多窗口的设计却又非常酷. iPhone上的Safari 4.3也和iPad差不多.这个多窗口的设计,影响了那个年代的第三方浏览器,UC啊,QQ啊都这么学.可是后来呢,iPad上的诸多第三方浏览器都是有标签栏的,Safari估计是被骂多了,在iOS5开始也这么干. 可是其实,iOS4的Safari代码里就已经有关于Tab的代码,可见Safari的重构优化是伴随着发布的,没做完的功能也

iphone SprintBoard部分私有API总结

本文介绍iOS SrpintBoard框架的部分私有API,具体包括: 获取ios上当前正在运行的所有App的bundle id(不管当前程序是在前台还是后台都可以) 获取ios上当前前台运行的App的bundle id(不管当前程序是在前台还是后台都可以) 根据ios app的bundle id得到其App名称.图标(不管当前程序是在前台还是后台都可以) 直接通过App 的bundle id来运行该App,无需使用url scheme(仅限当前程序在前台时,假如程序在后台能随便运行其他App,