2022-09-03

iOS: 触摸事件传递


1 用户点击屏幕后产生的一个触摸事件,经过一系列的传递过程后,会找到最合适的视图控件来处理这个事件
2 找到最合适的视图控件后,就会调用控件的touches方法来作具体的事件处理。touchesBegan…touchesMoved…touchedEnded…
3 这些touches方法的默认做法是将事件顺着响应者链条向上传递,将事件交给上一个响应者进行处理

我们可以试验一下,一个简单的VC的View只有一个SubView的App,对touches的处理,
如果我们不写next?.touchesBegan(touches, with: event),那么点击SubView只有一个print,因为当前的响应者就是那个subview

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
extension UIApplication {
open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("UIApplication touchesBegan")
next?.touchesBegan(touches, with: event)
}
}

extension UIViewController {
open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("UIViewController touchesBegan")
next?.touchesBegan(touches, with: event)
}
}

extension UIView {
open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("UIView touchesBegan")
next?.touchesBegan(touches, with: event)
}
}

带上next?.touchesBegan(touches, with: event),可以看到输出如下

1
2
3
4
5
6
UIView touchesBegan
UIViewController touchesBegan
UIView touchesBegan
UIView touchesBegan
UIView touchesBegan
UIApplication touchesBegan

我们也可以按照系统的思路实现自己的hitTest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class HitTestExampleView: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if !isUserInteractionEnabled || isHidden || alpha <= 0.01 {
return nil // 此处指视图无法接受事件
}
if self.point(inside: point, with: event) { // 判断触摸点是否在自身内部
for subview in subviews.reversed() { // 按 FILO 遍历子视图
let convertedPoint = subview.convert(point, from: self)
let resultView = subview.hitTest(convertedPoint, with: event)
// ⬆️这句是判断触摸点是否在子视图内部,在就返回视图,不在就返回nil
if resultView != nil { return resultView }
}
return self // 此处指该视图的所有子视图都不符合要求,而触摸点又在该视图自身内部
}
return nil // 此处指触摸点是否不在该视图内部
}
}