iOS – UI事件传递与响应者链

UI事件传递

UIView 内部包含一个 hitTest 方法,用于 UI 事件的传递。

当一个触摸事件发生时,事件传递顺序如下:

UIApplication -> UIWindow -> 寻找合适的View

假设颜色对应的 View 关系如下:

黄色:View 1
绿色:View 1_1
灰色:View 1_1_1
红色:View 1_2

View 1 定义如下:

class View1: UIView {

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        print("View 1 hitTest")

        return super.hitTest(point, with: event)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("View 1 touchesBegan")
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("View 1 touchesMoved")
    }

    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("View 1 touchesCancelled")
    }
}

同理,View11View111View12 定义类似于 View1

View 的层级关系如下:

View1 -> View11 -> View111

View1 -> View12

具体布局代码如下:

// view 1
let view1 = View1(frame: CGRect(x: 40, y: 100, width: 300, height: 400))
view1.backgroundColor = UIColor.yellow
self.view.addSubview(view1)

// view 1_1
let view11 = View11(frame: CGRect(x: 20, y: 30, width: 100, height: 120))
view11.backgroundColor = UIColor.green
view1.addSubview(view11)

// view 1_1_1
let view111 = View111(frame: CGRect(x: 10, y: 10, width: 60, height: 40))
view111.backgroundColor = UIColor.gray
view11.addSubview(view111)

// view 1_2
let view12 = View12(frame: CGRect(x: 20, y: 200, width: 100, height: 120))
view12.backgroundColor = UIColor.red
view1.addSubview(view12)

当一个触摸事件发生在灰色(View 1_1_1)区域时,hitTest 被调用的顺序如下。

View 1 hitTest
View 1_2 hitTest
View 1_1 hitTest
View 1_1_1 hitTest

表明触摸事件发生时,先从底层 View 开始逐级向上层进行遍历,直到找到灰色视图 View111

响应者链

寻找到合适的 View 后,将会响应对应 View 的触摸事件。

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    print("View 1_1_1 touchesBegan")
}

此处合适的 View 就是 View111,将输出日志:

View 1_1_1 touchesBegan

响应者链的传递顺序与 UI 事件传递顺序正好相反,如果当前 View (即:View 1_1_1)无法响应触摸事件(比如:isUserInteractionEnabled = false),将向父一级传递事件。此时,View 1_1 将收到触摸事件,如果 View 1_1 可交互的话,将响应对应的 touchesBegan 事件。

View 1_1 touchesBegan

否则继续往父一级遍历,直到找到合适的响应对象。

示例代码

GitHub: iOS-Examples/Example-View响应链