响应方法和响应事件传递
目录 响应方法和响应事件传递事件处理机制的应用 使用Hit-Test来扩大点击范围处理兄弟控件的事件 事件处理机制在系统控件中 参考资料
事件的种类
其中最常见的就是触摸事件,文章后续都已触摸事件为例来进行讲解。
事件响应链
首先iOS只有集成了的对象,才有资格成为事件响应者。常见的时间响应者对象包含、、、等组件。其次,在App中的组件是一个树状结构,在每个单例下可能会有多个,一个下有可能有多个,以此类推,因此iOS中,会形成一个事件响应链的形态。
事件响应者 如何成为事件响应者
只要是继承了的对象都可以成为事件响应者,但是一个事件产生之后第一件是是要找出这个事件的第一响应者。找出第一响应者的方法叫做hit-test。
Hit-Test
可以成为第一响应者的对象除了需要继承以外,还有一个更严格的条件,除对象以外,其余的对象如果想要成为第一响应者一定要继承,只有才有资格成为第一响应者。
类中包含两个方法
- (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event; // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event; // default returns YES if point is in bounds
第二个::方法仅在::方法中才会调用。
找到事件的第一响应者的流程如下:
系统在检测到用户的触摸事件后,将事件以及时间的参数(比如触摸点的坐标)等数据作为参数,向当前的单例对象发送消息。单例收到之后,会依次调用显示的对象的::方法,如果方法返回了一个对象,则这个对象就是第一响应者,如果返回为空,则对象就是第一响应者。在::方法中,会调用::方法来判断该事件是否属于当前对象,如果不属于,就直接返回空。如果属于当前对象,则会继续遍历其,调用每个的::方法。如果所有该方法都返回了空,那么就返回当前对象为第一事件响应者。以此类递归找到最终的子组件,这样就可以找到第一事件响应者了。
响应方法和响应事件传递
类中包含如下的方法:
// 下一个响应者
open var next: UIResponder? { get }// 第一响应者相关方法
open var canBecomeFirstResponder: Bool { get } // default is NO
open func becomeFirstResponder() -> Bool
open var canResignFirstResponder: Bool { get } // default is YES
open func resignFirstResponder() -> Bool
open var isFirstResponder: Bool { get }// 触摸事件
open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
open func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?)
open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?)
open func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?)
@available(iOS 9.1, *)
open func touchesEstimatedPropertiesUpdated(_ touches: Set<UITouch>)// 运动事件
open func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?)
open func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?)
open func motionCancelled(_ motion: UIEvent.EventSubtype, with event: UIEvent?)// 远程控制事件
open func remoteControlReceived(with event: UIEvent?)// 按压事件
open func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?)
open func pressesChanged(_ presses: Set<UIPress>, with event: UIPressesEvent?)
open func pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?)
open func pressesCancelled(_ presses: Set<UIPress>, with event: UIPressesEvent?)
主要分为三个模块:
下一个响应者对象一些关于第一响应者的方法事件回调方法(响应处理的地方)
这里有一个值得注意的地方,对象中的这个第一响应者,和之前Hit-Test得到的第一响应者并不同。通过测试发现,如果我们有A、B、C三个View,然后通过重写Hit-Test的方法控制B成为第一响应者,但是在页面加载出来之后调用C或者A的方法,却看不到任何变化。
但是目前看来这个方法的用途是控制系统键盘的弹出和隐藏。
首先,通过Hit-Test得到的第一响应者会优先触发它的时间回调方法,如果它并未重写该事件的方法,则会调用的实现,的实现就是找到下一个响应者对象,并且调用它的该事件方法,以此类推。
因此,如果一个响应者想要拦截这个事件,不让它继续往下传,只需要重写这个事件的回调方法,并且不调用super的这个方法就可以了。
在事件回调方法中包含NSSet对象和对象,NSSet对象中包含一个或者多个对象,下面我们简单介绍一下这两个重要的类,和
事件处理机制的应用 手势控制器
UIKit封装有类及它的基类来简化手势的监听处理。也是基于上述的事件回调方法来实现的,只是做了进一步的封装
手势控制器的种类 使用Hit-Test来扩大点击范围
可以重写的::方法,来定义控件的响应范围,多用来增大按钮的点击范围或者增大的滑动范围。
处理兄弟控件的事件
可以重写的::方法,定义控件的响应范围为兄弟组件的显示范围,一次来通过在兄弟控件上的触控,响应自己的事件。
事件处理机制在系统控件中
目前一些系统组件有重写事件回调方法,并且做了一些特殊的事情。以下是两个例子
重写了和方法,用来实现点击的高亮效果。
重写了和方法,用来检测用户是不是真的有意图去滑动。如果用户在300ms之内并未有移动的事件,则会调用super方法,将事件处理权下发到子组件,但如果300ms内有移动,则就不会调用super方法,只是执行滑动效果。
参考资料
iOS事件机制
开发者官方文档
Touch 开发者官方文档