Gesture Recognizers与触摸事件分发

一.Gesture Recognizers

Gesture Recognizers是在iOS3.2引入的,可以用来识别手势、简化定制视图事件处理的对象。Gesture Recognizers的基类为UIGestureRecognizer,这一个抽象基类,定义了实现底层手势识别行为的编程接口。在UIKit框架中提供了6个具体的手势识别类,用来识别常见的手势。这6个手势识别器类为:

UITapGestureRecognizer:用来识别点击手势,包括单击,双击,甚至三击等。

UIPinchGestureRecognizer:用来识别手指捏合手势。

UIPanGestureRecognizer:用来识别拖动手势。

UISwipeGestureRecognizer:用来识别Swipe手势。

UIRotationGestureRecognizer:用来识别旋转手势。

UILongPressGestureRecognizer:用来识别长按手势。

为了识别手势,需要将Gesture Recognizers关联到其检测触摸事件的view上,可以使用UIViewaddGestureRecognizer:方法将手势识别器绑定到视图上。Gesture Recognizers在触摸事件处理流程中,处于观察者的角色,其不是view层级结构的一部分,所以也不参与responder chain。在将触摸事件发送给hit-test view之前,系统会先将触摸事件发送到hit-test view上绑定的或hit-test view父视图(superview)上绑定的Gesture Recognizers上。其流程大概如下图所示:

注:图中view与Gesture Recognizer的关系是,Gesture Recognizer关联在view或view的superview(可能多级)上。

二.Gesture Recognizers与事件分发路径的关系

Gesture Recognizers可能会延迟将触摸事件发送到hit-test view上,默认情况下,当Gesture Recognizers识别到手势后,会向hit-test view发送cancel消息,来取消之前发给hit-test view的事件。控制这个流程的是UIGestureRecognizer的三个属性

cancelsTouchesInView(默认为YES)

delaysTouchesBegan(默认为NO)

delaysTouchesEnded(默认为YES)

cancelsTouchesInView为YES,表示当Gesture Recognizers识别到手势后,会向hit-test view发送 touchesCancelled:withEvent:消息来取消hit-test view对此触摸序列的处理,这样只有Gesture Recognizers能响应此触摸序列,hit-test view不再响应。如果为NO,则不发送touchesCancelled:withEvent:消息给hit-test view,这样会使Gesture Recognizers和hit-test view同时响应触摸序列。

delaysTouchesBegan为NO,表示触摸序列开始时,而手势识别器还未识别出此手势时,touch事件会同时发向hit-test view,这样在手势识别器还未识别出此手势,hit-test view同时也可以收到同样的触摸事件。如果为YES,则在手势识别器在识别手势的过程中,不会有任何触摸事件发送给hit-test view,如果手势识别器最终识别到了手势,则也不会发送任何消息(包括touchesCancelled:withEvent:)给hit-test view;若干手势识别最终没有识别到手势,则所有的触摸事件在发给hit-test view处理。关于这个特性,可参考UIScrollViewdelaysContentTouches属性。这样属性也谨慎使用,使用不当会导致UI无响应。

delaysTouchesEnded,在文档上的解释是,当手势识别器在识别手势时,对于UITouchPhaseEnded阶段的touch会延迟发送给hit-test view,在手势识别成功后,发送给hit-test view cancel消息,手势识别失败时,发送原来的end消息。其给出了了这样的例子识别双击操作的UITapGestureRecognizer对象,其numberOfTapsRequired设为2,在用户进行双击操作时,如果delaysTouchesEnded为NO,则hit-test view中的调用序列为

touchesBegan:withEvent:,

touchesEnded:withEvent:,

touchesBegan:withEvent:,

and touchesCancelled:withEvent:

如果delaysTouchesEnded为YES,则调用序列为:

touchesBegan:withEvent:,

touchesBegan:withEvent:,

touchesCancelled:withEvent:,

touchesCancelled:withEvent:

但我在实际测试时,并非如此,实际测试的结果是,如果delaysTouchesEnded为NO,则调用序列为:

touchesBegan:withEvent:,

touchesEnded:withEvent:,

TapGestureRecognizer 检测到双击

如果delaysTouchesEnded为YES,则调用序列为:

touchesBegan:withEvent:,

touchesEnded:withEvent:,

TapGestureRecognizer 检测到双击

touchesCancelled:withEvent:

这个问题还没搞清楚!

三.多个Gesture Recognizer之间的关系

在一个view上可以绑定多个Gesture Recognizer,在默认情况下,触摸序列中的触摸事件会以不确定的次序在各个gesture

recognizer中传递,直到事件最终发送给hit-test view(如果中间没被Gesture

Recognizer识别出并截获的话)。多个Gesture Recognizer之间的关系也可以根据需要定制,主要有下面几种行为

1.使其中一个gesture recognizer失败的情况下,另一个gesture recognizer才能分析事件。

以同时识别单击操作和双击操作为例,两个gesture recognizers分别用来识别单击和双击,分别为singleTapGesture和doubleTapGesture。在默认情况下,当用户进行单击操作时,singleTapGesture会识别出一个单击操作,doubleTapGesture也会识别出一个双击动作,但我们的意图是,这仅仅是一个双击操作。在这种情况下我们可以使用UIGestureRecognizer的requireGestureRecognizerToFail:方法来使singleTapGesture在doubleTapGesture识别识别的时候才分析事件,如果doubleTapGesture识别出双击事件,则singleTapGesture不会有任何动作。

[singleTapGesture requireGestureRecognizerToFail:doubleTapGesture];

需要注意的是,在这种情况下,如果用户进行单击操作,需要一段延时(即doubleTapGesture识别失败),singleTapGesture才会识别出单击动作,进行单击处理,这段时间很多,对实际使用几乎没有影响。

2.精确控制gesture recognizer是否响应某个事件或事件序列.

UIGestureRecognizerDelegate协议中有两个可选方法可以控制gesture recognizer是否需要识别某些事件

gestureRecognizerShouldBegin:

此方法在gesture recognizer视图转出UIGestureRecognizerStatePossible状态时调用,如果返回NO,则转换到UIGestureRecognizerStateFailed;如果返回YES,则继续识别触摸序列.(默认情况下为YES)

gestureRecognizer:shouldReceiveTouch:

此方法在window对象在有触摸事件发生时,调用gesture recognizer的touchesBegan:withEvent:方法之前调用,如果返回NO,则gesture recognizer不会看到此触摸事件。(默认情况下为YES).

另外,在UIGestureRecognizer类中也有两个可以重写的方法来完成与Delegate方法中相同的功能

-

(BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer;

-

(BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer *)preventingGestureRecognizer;

3.允许多个手势识别器共同识别

默认情况下,两个gesture recognizers不会同时识别它们的手势,但是你可以实现UIGestureRecognizerDelegate协议中的

gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:方法对其进行控制。这个方法在这两个gesture recognizers中的任意一个将block另一个的触摸事件时调用,如果返回YES,则两个gesture recognizers可同时识别,如果返回NO,则并不保证两个gesture recognizers必不能同时识别,因为另外一个gesture recognizer的此方法可能返回YES。也就是说两个gesture recognizers的delegate方法只要任意一个返回YES,则这两个就可以同时识别;只有两个都返回NO的时候,才是互斥的。默认情况下是返回NO。

有这样一个例子,如果要侦测在window上的所有触摸事件,可以将gesture recognizer关联到window上,默认情况下如果手势被window识别,则子视图中的gesture recognizer就失效了,而我们在window上的gesture recognizer的目的只是监控所有事件,但并不处理这些事件,具体事件的处理还需要子视图中的各个gesture recognizer去处理,这样我们可以实现window上绑定gesture recognizer的delegate方法,使gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:返回YES即可。

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {

return YES;

}

四.UIScrollView的类似行为

scroll view没有滚动栏,当在scroll view上有触摸行为时其要识别出触摸行为的目的是scroll view本身还是其内容子视图。定制scrollview如何处理这种情况,看查看UIScrollView类的下列属性和方法。

touchesShouldBegin:withEvent:inContentView:

touchesShouldCancelInContentView:

canCancelContentTouches

delaysContentTouches


IOS开发之手势——UIGestureRecognizer 共存

在 iPhone 或 iPad 的开发中,除了用touchesBegan / touchesMoved / touchesEnded这组方法来控制使用者的手指触控外,也可以用UIGestureRecognizer的衍生类別来进行判断。用UIGestureRecognizer的好处在于有现成的手势,开发者不用自己计算手指移动轨迹。UIGestureRecognizer的衍生类別有以下几种:

UITapGestureRecognizer

UIPinchGestureRecognizer

UIRotationGestureRecognizer

UISwipeGestureRecognizer

UIPanGestureRecognizer

UILongPressGestureRecognizer

从命名上不难了解這些类別所对应代表的手势,分別是 Tap(点一下)、Pinch(二指往內或往外拨动)、Rotation(旋转)、Swipe(滑动,快速移动)、Pan (拖移,慢速移动)以及 LongPress(长按)。這些手势別在使用上也很简单,只要在使用前定义并添加到对应的视图上即可。

//定义一个 recognizer, 并加到需要偵測该手势的 UIView 元件上

- (void)viewDidLoad {

UISwipeGestureRecognizer* recognizer;

//handleSwipeFrom 是偵測到手势,所要呼叫的方法

recognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:selfaction:@selector(handleSwipeFrom)];

//不同的 Recognizer 有不同的实体变数

//例如 SwipeGesture 可以指定方向

//而 TapGesture 則可以指定次數

recognizer.direction = UISwipeGestureRecognizerDirectionUp

[self.view addGestureRecognizer:recognizer];

[recognizer release];

}

- (void)handleSwipeFrom:(UISwipeGestureRecognizer*)recognizer {

//触发手勢事件后,在这里作些事情

//底下是刪除手势的方法

[self.view removeGestureRecognizer:recognizer];

}

问题來了。有些手势其实是互相关联的,例如 Tap 与 LongPress、Swipe与 Pan,或是 Tap 一次与Tap 兩次。当一個 UIView 同时添加兩个相关联的手势时,到底我这一下手指头按的要算是 Tap 还是 LongPress?如果照預设作法来看,只要「先滿足条件」的就会跳出并呼叫对应方法,举例来说,如果同时注册了 Pan 和 Swipe,只要手指头一移动就会触发 Pan 然后跳出,因而永远都不會发生 Swipe;单点与双点的情形也是一样,永远都只会触发单点,不會有双点。

那么这个问题有解吗?答案是肯定的,UIGestureRecognizer有个方法叫做requireGestureRecognizerToFail,他可以指定某一个 recognizer,即便自己已经滿足條件了,也不會立刻触发,会等到该指定的 recognizer 确定失败之后才触发。以同时支持单点与双点的手势为例,代码如下:

- (void)viewDidLoad {

//单击的 Recognizer

UITapGestureRecognizer* singleRecognizer;

singleRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:selfaction:@selector(handleSingleTapFrom)];

singleTapRecognizer.numberOfTapsRequired =1;//单击

[self.view addGestureRecognizer:singleRecognizer];

//双击的 Recognizer

UITapGestureRecognizer*double;

doubleRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:selfaction:@selector(handleDoubleTapFrom)];

doubleTapRecognizer.numberOfTapsRequired =2;//双击

[self.view addGestureRecognizer:doubleRecognizer];

//关键在这一行,如果双击确定偵測失败才會触发单击

[singleRecognizer requireGestureRecognizerToFail:doubleRecognizer];

[singleRecognizer release];

[doubleRecognizer release];

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容