事件响应链的一个bug
源于同事遇到的一个问题,简化情况:

红色的View
是加在ViewController
上的一个自定义View
:
代码如下:
import UIKit
class RedView: UIView {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("red touchesBegan")
self.next?.touchesBegan(touches, with: event)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print("red touchesMoved")
self.next?.touchesMoved(touches, with: event)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
print("red touchesEnded")
self.next?.touchesEnded(touches, with: event)
}
}
事件响应的原理是,如果这个View
没有处理这个事件(即没有实现touchesXXX
等方法),那么这个事件直接传递给superView
处理,这里的superView
当然就是ViewController
里面的View,如果这个View没有处理,那么交给所属的ViewController
处理。具体的事件响应链这里不做讨论。
那么这里的情况是,RedView
做了事件处理,并且在处理之后,又原封不动的把事件抛给事件响应链里的下一个接收者。这里的self.next
就是获取下一个事件接收者。
ViewController
代码:
import UIKit
class ViewController: UIViewController {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("vc touchesBegan")
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print("vc touchesMoved")
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
print("vc touchesEnded")
}
}
我点击red View会有什么打印?
的确如预期的那样。

如果我想触发touchesMoved
,也就是在red View滑动,会是什么结果?

只有red view会持续接收到touchesMoved
事件。vc接收到一次就终止了,就连最后的touchesEnded
事件也没有接收到。
这种情况只会发生在ViewController里的View上。也就是如果是两个自定义view的话,不会有这个问题?
那么究竟是什么问题?从事件响应链上来说,并没有什么特殊的地方,难道是ViewController里的View有什么特殊处理吗?
通过下面stackoverflow讨论的结果是:
Passing touchesMoved events to superview only passes first event
touchesMoved not firing in iOS5 for the iPhone for UIScrollView
-
1.iOS5之后出现这个问题,iOS之前是正常的。
-
2.官方已经给出答复,这个是iOS的bug。
-
3.只能用其他方式替代。
最直接的解决方法是,将上面的red View代码改为
import UIKit
class RedView: UIView {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("red touchesBegan")
self.next?.next?.touchesBegan(touches, with: event)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print("red touchesMoved")
self.next?.next?.touchesMoved(touches, with: event)
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
print("red touchesEnded")
self.next?.next?.touchesEnded(touches, with: event)
}
}
将self.next?
改为self.next?.next?
,意思是,将事件传递到下下个事件接收者,也就是ViewController
,而不再是ViewController
里的View
。我们可以得到想要的结果:
