美文网首页程序员
Swift UIResponder事件响应链分析

Swift UIResponder事件响应链分析

作者: KaKaLaaa | 来源:发表于2020-08-13 10:51 被阅读0次

    前言

    该篇文章为记录回顾自己成长历程的开始,文笔不好,若有技术性错误,请各位大佬不吝批评指点~

    查找第一响应者

    在查找第一响应者的时候,有两个核心的API,它的原理就是通过不断调用子视图的这两个API完成的。

    • 调用下面的方法,用来获取到被用户点击的视图,即第一响应者。
    open func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
    
    • hitTest: with event: 方法内部会通过调用下面的方法,来判断用户点击的区域是否在视图上,是则返回true,不是则返回false。
    open func point(inside point: CGPoint, with event: UIEvent?) -> Bool
    
    • 下面的两个方法则是处理控件上的坐标点转换。
    open func convert(_ point: CGPoint, to view: UIView?) -> CGPoint
    open func convert(_ point: CGPoint, from view: UIView?) -> CGPoint
    

    应用程序接收到事件之后,会将事件交给keyWindow并转发给根视图,根视图按照视图的层级逐级遍历子视图,并且遍历过程中不断判断视图的范围,并最终找到第一响应者。

    事件传递响应过程

    1.UIApplication接收到事件,再将事件传递给keyWindow;
    2.keyWindow遍历subViews的hitTest: with event: 方法,找到用户点击区域内合适的视图来处理事件;
    3.UIView的子视图也会遍历其subViews的hitTest: with event: 方法,以此类推;
    4.直到找到点击区域内,且处于最上方的视图,再将视图逐步返回给UIApplication;
    5.在查找第一响应者的过程中,就已经形成了一个响应者链,此时应用程序将会先调用第一响应者处理事件;
    6.如果第一响应者不能处理事件,则调用其next属性,一直找响应者链中能处理该事件的对象;
    7.最后到UIApplication仍没有可以处理该事件的对象,则该事件被废弃;

    注意⚠️ UIView不接受事件处理的情况主要有以下几种情况:

    • alpha <= 0.01
    • isUserInteractionEnabled = false
    • isHidden = true
    • 超出父控件的响应区域

    事件传递流程图

    (盗来的图,勿喷,尊重原创作者~)


    事件传递流程图.png

    示例代码

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
            
        let view = super.hitTest(point, with: event)
        if self.point(inside: point, with: event) {
            // 对子视图从上往下遍历查找
            for subView in self.subviews {
                // 将父坐标点转换至子视图(如UIButton)坐标点上
                let btnPoint = subView.convert(point, from: self)
                if let btn = subView as? UIButton {
                    // 判断用户点击区域是否在按钮上,是则返回按钮为第一响应者
                    if btn.point(inside: btnPoint, with: event) { return btn }
                }
            }
            // 如果在遍历过程中用户点击区域均找不到第一响应者,则往下另外查找第一响应者,有则返回。
            let hitView = self.delegate?.hitTestView()
            return hitView
        } else {
            // 由系统处理用户点击事件
            return view
        }
    }
    

    Gesture Recognizer

    如果视图有事件到来的时候,且视图有附加的手势动作,则手势识别优先处理事件,如果手势识别没有处理事件,则将事件交给视图本身自行处理,视图如果未处理则顺着响应者链继续向后传递。

    当响应者链和手势同时出现,也就是既实现了touches方法又添加了手势,我们会发现touches方法有时会失效,这是因为\color{red}{手势的执行优先级是高于响应者链的}

    在UIApplication向第一响应者派发事件,并且遍历响应者链查找手势时,会开始执行响应者链中的touches系列方法。会先执行touchesBegan和touchesMoved方法,如果响应者链能够继续响应事件,则执行touchesEnded方法表示事件完成,如果将事件交给手势处理则调用touchesCancelled方法将响应者链打断。

    根据Apple的官方文档,手势不参与响应者链传递事件,但是也通过hitTest的方式查找响应的视图,手势和响应者链一样都需要通过hitTest方法来确定响应者链。在UIApplication向响应者链派发消息时,\color{red}{只要响应者链中存在能够处理事件的手势,则手势响应事件,}如果手势不在响应者链中则不能处理事件。

    Apple官方文档:Apple UIGestureRecognizer 文档

    UIControl

    根据上面的手势和响应者链的处理规则,我们会发现UIButton、UISlider等控件,并不符合这个处理规则。UIButton可以在其父视图已经添加UITapGestureRecognizer的情况下,依然正常响应事件,并且tap手势不响应。


    image.png

    以UIButton为例,UIButton也是通过hitTest的方式查找第一响应者。区别在于,如果UIButton是第一响应者,则直接由UIApplication派发事件,不通过Responder Chain派发。如果其不能处理事件,则交给手势处理或响应者链传递。

    不只UIButton是直接由UIApplication派发事件,所有继承自UIControl的类,都是由UIApplication直接派发事件的。

    Apple官方文档:Apple UIControl 文档

    结束啦~~~文章多有摘抄其他各位大佬的技术逻辑等,写文章是记录和加深自身技术的一种非常好的途径,望大佬们多指点。

    部分资料来源于网络,若侵权,请联系删除~
    联系方式:kim77895pl@gmail.com
    Kim 写于2020.8.13,希望文章能对你有所帮助。

    相关文章

      网友评论

        本文标题:Swift UIResponder事件响应链分析

        本文链接:https://www.haomeiwen.com/subject/cxrmdktx.html