iOS事件机制(点击、手势、UIControl)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS事件机制(点击、手势、UIControl)相关的知识,希望对你有一定的参考价值。

参考技术A

注意:对于一个手指的触摸,是UITouch每次状态改变的时候都会回调UIResponder相对应的处理方法。对于多个手指的触摸,也许多个UITouch状态的改变一起回调UIResponder的处理方法,也许每个UITouch状态的改变都会回调UIResponder的处理方法,例如,两个点击,可能只有一个touchesBegan的回调,两个touchesEnded的回调.同时,多个UIControl状态改变只有一次touchesBegan等方法回调的参数touches里touch的个数我测试的时候只有一个,不要以为所有状态改变的UITouch只有一次回调时都会放到touches参数里。关于多点触摸的处理个人不建议在UITouch的响应机制里去做处理,里面具体原理并不明朗,实际开发中的借鉴也不多,涉及多点触摸使用手势更好。

UIResponder是ios中用于处理用户事件的API,可以处理触摸事件、按压事件(3D touch)、远程控制事件、硬件运动事件。可以通过touchesBegan、pressesBegan、motionBegan、remoteControlReceivedWithEvent等方法,获取到对应的回调消息。UIResponder不只用来接收事件,还可以处理和传递对应的事件,如果当前响应者不能处理,则转发给其他合适的响应者处理。
应用程序通过响应者来接收和处理事件,响应者可以是继承自UIResponder的任何子类,例如UIView、UIViewController、UIApplication等。当事件来到时,系统会将事件传递给合适的响应者,并且将其成为第一响应者。
第一响应者未处理的事件,将会在响应者链中进行传递,传递规则由UIResponder的nextResponder决定,可以通过重写该属性来决定传递规则。当一个事件到来时,第一响应者没有接收消息,则顺着响应者链向后传递。

Gesture Recognizer 是对底层事件处理的封装,是为了让使用者能够更简单处理事件。
手势分为离散型手势(discrete gestures)和持续型手势(continuous gesture)。

手势响应过程:

手势状态:

UIControl是系统提供的能够以target-action模式处理触摸事件的控件,iOS中UIButton、UISegmentedControl、UISwitch等控件都是UIControl的子类。

值得注意的是,UIConotrol是UIView的子类,因此本身也具备UIResponder应有的身份。

UIControl作为控件类的基类,它是一个抽象基类,我们不能直接使用UIControl类来实例化控件,它只是为控件子类定义一些通用的接口,并提供一些基础实现,以在事件发生时,预处理这些消息并将它们发送到指定目标对象上。

UIControl的触发过程:

四个重要识别方法是在touchesBegan、touchesMoved、touchedEnded、touchesCancelled里回调的。

推测是:endTrackingWithTouch调用后识别了行为,做标记,返回到touchesEnded后,判断本UIControl是否易识别行为,调用行为回调。

App接收到触摸事件后,会被放入当前应用程序的UIApplication维护的事件队列中.
由于事件一次只有一个,但是能够响应的事件的响应者众多,所以这就存在一个寻找第一响应者的过程。
调用方法,获取到被点击的视图,也就是第一响应者。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;

hitTest:withEvent:方法内部会通过调用pointInside:这个方法,来判断点击区域是否在视图上,是则返回YES,不是则返回NO。

经过Hit-Testing的过程后,UIApplication已经知道了第一响应者是谁,接下来要做的事情就是:

自定义的view的touchesBegan、touchesMoved、touchesEnded、touchedCancelled四个方法重写,记录打印过程,该view上添加tapGestureRecognized手势,该tapGestureRecognized也覆写了这四个方法。

点击view调用打印过程输出:

调用栈:

结合上面的输出和调用栈,我们可能并不能明确的看出有手势的时候点击的过程,不过如果你自己调试,是能得出如下结论的:

UIGestureRecognizer和UITouch的关系可以由UIGestureRecognizer的三个属性影响:cancelsTouchesInView、delaysTouchesBegan、delaysTouchesEnded。

本身就是在UIResponder的UITouchesBegan、UITouchesMoved、UITouchedEnded、UITouchesCancel四个回调中调用的。
UIControl的响应处理并不会影响UIResponder的响应链的处理,但是UIControl会影响另一个UIControl,子视图的UIControl具有优先级。

UIGestureRecognizer和UIControl并没有决定的优先级。
从iOS6开始在控件的父视图上面添加相应的手势,控件就会控制阻止手势行为,比如:
tap 手势在 UIButton,UISwitch,UIStepper,UISegmentControl,UIPageControl;
swipe 手势在 UISlider;
pan 手势在 UISwitch;
其他可能是手势优于控件的行为。

UIResponder有touchesBegan等四个方法,默认向superview传递。
所有需要自定义点击处理逻辑的UIResponder子类要覆盖这四个方法。
点击事件由四个方法处理。
UIButton的处理也是需要经过这四个方法。
UIGestureRecognizer也有touchesBegan等四个方法。
手势不在响应链里,但是也会观察它的view和subView的点击。

UIGestureRecognizer会影响UIResponder的四个响应点击的方法。
默认点击事件响应关键步骤说明:
1)用户手指点击屏幕,经过系统传递到UIApplication, UIApplication通过hitTest:方法找到对应UITouch发生的第一响应者view
2)UIApplication更新手势状态,从第一响应者上的手势到其视图层上所有先辈视图上的手势都会接收这个UITouch来更新手势状态
3)UIApplication将UITouch交给找到的第一响应着view处理
4)UIApplication更新手势状态,识别成功后,会向UITouch的第一响应者发送cancel方法

加上UIControl会让过程变得复杂,关于UIControl的原理,不清楚,也不敢妄下结论,依据网上和实际测试大致推断:
1)它不会影响UITouch本身的响应流程,但是会影响其他UIControl和UIGestureRecognizer的响应
2)自定义的UIControl是和UITouch本身的响应过程是一样的
3)系统定义的UIControl和UIGestureRecognizer同一个优先级,谁先识别出来,另一个就out了,但是UIControl和UIGestureRecognizer有一点不同,它并不会cancel UITouch的流程。

关于UITouch、UIGestureRecognizer、UIControl之间影响说明:
1)UITouch和UIGestureRecognizer:UIGestureRecognizer优先级高于UITouch,由UIGestureRecognizer的三个参数cancelsTouchesInView、delaysTouchesBegan、delaysTouchesEnded决定对UITouch的影响,默认情况下,UIGestureRecognizer识别成功后,会向UITouch发送cancel
避免:
1)尽量不要覆盖重写UIResponder的touchesBegin、touchesMoved、touchesCancelled、touchesEnded这四个方法,如果需要覆盖重写,逻辑应该尽量简单,不宜做复杂的处理,
2)不要自定义UIControl,直接使用系统定义的UIControl
3)UIControl上不要添加UIControl子视图
4)不要依赖UIGestureRecognizer的delayTouchBegin和delayTouchEnded
5)不要自定义UIGestureRecognizer

参考文章:
1) iOS 事件(UITouch、UIControl、UIGestureRecognizer)传递机制
2) Touch Event Handing 教学 — part 1

使 UIControl 仅接受滑动手势并传递触摸/点击手势

【中文标题】使 UIControl 仅接受滑动手势并传递触摸/点击手势【英文标题】:Make UIControl only accept swipe gestures and pass along touch/tap gestures 【发布时间】:2014-10-22 06:22:27 【问题描述】:

我正在使用 Xcode 6.1 在 Objective-C 中为 iPhone 5 开发 iOS 8.1 应用程序。

单击UITextView 时,我会在键盘上方弹出一个不可见的UIControl 视图,以便用户可以从键盘上方向下滑动并关闭键盘(然后移动UIControl又看不见了)。这工作正常。然而,这个在键盘上方弹出的UIControl 视图覆盖了另一个UITextView,因此无法点击被覆盖的文本视图。每次我尝试点击覆盖的文本视图(因为UIControl 不是不透明的所以可见)时,什么都没有发生,因为UIControl 似乎只是在点击而不对它们做任何事情。

我的问题是,我怎样才能让UIControl 简单地忽略点击(让它们直接通过,以便下面的UITextView 可以接受它们),但接受滑动(这样,当它是向下滑动,它可以关闭键盘并移出视线)?

我尝试了几种解决方案,但没有找到一种可以很好地满足我的需求。

谢谢!

【问题讨论】:

【参考方案1】:

此question 与您的相似。将您的 UIControl 子类化并覆盖 - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event 并调用其 super 变体以将其传递给它的视图。

【讨论】:

感谢您引导我回答这个问题。我没有时间测试解决方案(我已经从这个问题上移开了),但没有任何其他答案比这更好,所以我将其标记为已接受。跨度> 【参考方案2】:

还有另一种方法可以做到这一点。将UIViewController 的主视图更改为UIControl 的子类,而不是UIView。将以下IBAction 连接到视图,以便在点击背景时关闭键盘。

- (IBAction)backgroundTapped

    [self.view endEditing:YES];


Apple Documentation - endEditing:

【讨论】:

这是否仍允许该主视图的子视图接收手势?它不会自己处理所有手势吗? @EthanG 不会影响UITextField,你试过了吗? 我还没有尝试过,因为我仍然想要一个类似于我描述的解决方案。我只是在问,以防我最终不得不退回到这个替代方案。谢谢。【参考方案3】:

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer

使用此事件。如果点击手势返回no。

【讨论】:

唯一的问题是,我不知道UIControl 只是忽略手势。我希望手势通过它传递到下面的任何视图。我认为您在此处提供的建议不会做到这一点,对吗?

以上是关于iOS事件机制(点击、手势、UIControl)的主要内容,如果未能解决你的问题,请参考以下文章

自定义 UIControl 和手势识别器

iOS开发系列--触摸事件手势识别摇晃事件耳机线控

网易有道 iOS面经

iOS开发:Runtime解决UITapGesture重复点击问题

iOS解决单击手势和UITableView的点击事件冲突

UIControl 子类 - 如何停止触摸事件流?