为什么 clipsToBounds 会阻止子视图被触摸?
Why does clipsToBounds prevent the subviews to be touched?
假设您有一个比其子视图更小的父视图。您将父视图的 clipsToBound
属性 设置为 false
。如果点击子视图超出父视图边界的突出区域,为什么命中测试 return nil?
我的理解是命中测试从子视图开始,一直到父视图。那么为什么要晚于子视图进行测试的父视图的 属性 很重要呢?或者命中测试是否从根开始到树视图,如视图控制器 -> 主视图 -> 子视图?
我从 here 中找到了一个自定义命中测试,它允许您点击父视图边界之外的子视图区域,但我不确定为什么颠倒子视图的顺序一个区别(它有效,我只是不确定为什么)。我的例子甚至只有一个子视图。
class ViewController: UIViewController {
let superview = CustomSuperview(frame: CGRect(origin: .zero, size: .init(width: 100, height: 100)))
let subview = UIView(frame: CGRect(origin: .zero, size: .init(width: 200, height: 200)))
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.superview)
self.superview.backgroundColor = .red
self.superview.clipsToBounds = false
self.superview.addSubview(self.subview)
self.subview.backgroundColor = .blue
let tap = UITapGestureRecognizer(target: self, action: #selector(tapped))
self.subview.addGestureRecognizer(tap)
}
@objc func tapped(_ sender: UIGestureRecognizer) {
print("tapped")
}
}
class CustomSuperview: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if clipsToBounds || isHidden || alpha == 0 {
return nil
}
for subview in subviews.reversed() {
let subPoint = subview.convert(point, from: self)
if let result = subview.hitTest(subPoint, with: event) {
return result
}
}
return nil
}
}
My understanding is that the hit test starts from the subview and work its way up to the superview.
那你的理解就完全错误了。让我们解决这个问题。首先,让我们澄清一些其他的误解:
reversed
完全是转移话题;它与它无关。子视图总是以相反的顺序进行测试,因为前面的子视图需要优先于后面的子视图。
clipsToBounds
也完全是转移注意力。它所做的只是改变您是否可以查看 其父视图之外的子视图;它对您是否可以触摸其父视图之外的子视图没有任何影响。
好的,这是如何工作的?让我们以包含子视图 A 的视图 V 为例。假设 A 在 V 之外。假设您可以看到 A,然后点击 A。
现在开始命中测试。但事情是这样的:它从 window 的级别开始,并按照视图层次结构 向下 的方式工作。所以 window 首先询问它的子视图;只有一个,视图控制器的主视图。
所以现在我们递归,视图控制器的主视图询问它的个子视图。其中之一是 V。“嘿,V,水龙头在你体内吗?” “不!” (你必须同意这是正确答案,因为我们已经说过 A 在 V 之外。)
所以视图控制器的主视图放弃了 V,并且永远不会发现水龙头在A上,因为我们从来没有递归到那么远。所以它向链上报告:“水龙头不在我的子视图的 any 上,所以我必须报告水龙头在 me 上”点击 通过 进入视图控制器的主视图。
但是您可以通过覆盖 hitTest
:
的实现来改变 那个行为
override func hitTest(_ point: CGPoint, with e: UIEvent?) -> UIView? {
if let result = super.hitTest(point, with:e) {
return result
}
for sub in self.subviews.reversed() {
let pt = self.convert(point, to:sub)
if let result = sub.hitTest(pt, with:e) {
return result
}
}
return nil
}
假设您有一个比其子视图更小的父视图。您将父视图的 clipsToBound
属性 设置为 false
。如果点击子视图超出父视图边界的突出区域,为什么命中测试 return nil?
我的理解是命中测试从子视图开始,一直到父视图。那么为什么要晚于子视图进行测试的父视图的 属性 很重要呢?或者命中测试是否从根开始到树视图,如视图控制器 -> 主视图 -> 子视图?
我从 here 中找到了一个自定义命中测试,它允许您点击父视图边界之外的子视图区域,但我不确定为什么颠倒子视图的顺序一个区别(它有效,我只是不确定为什么)。我的例子甚至只有一个子视图。
class ViewController: UIViewController {
let superview = CustomSuperview(frame: CGRect(origin: .zero, size: .init(width: 100, height: 100)))
let subview = UIView(frame: CGRect(origin: .zero, size: .init(width: 200, height: 200)))
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.superview)
self.superview.backgroundColor = .red
self.superview.clipsToBounds = false
self.superview.addSubview(self.subview)
self.subview.backgroundColor = .blue
let tap = UITapGestureRecognizer(target: self, action: #selector(tapped))
self.subview.addGestureRecognizer(tap)
}
@objc func tapped(_ sender: UIGestureRecognizer) {
print("tapped")
}
}
class CustomSuperview: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if clipsToBounds || isHidden || alpha == 0 {
return nil
}
for subview in subviews.reversed() {
let subPoint = subview.convert(point, from: self)
if let result = subview.hitTest(subPoint, with: event) {
return result
}
}
return nil
}
}
My understanding is that the hit test starts from the subview and work its way up to the superview.
那你的理解就完全错误了。让我们解决这个问题。首先,让我们澄清一些其他的误解:
reversed
完全是转移话题;它与它无关。子视图总是以相反的顺序进行测试,因为前面的子视图需要优先于后面的子视图。clipsToBounds
也完全是转移注意力。它所做的只是改变您是否可以查看 其父视图之外的子视图;它对您是否可以触摸其父视图之外的子视图没有任何影响。
好的,这是如何工作的?让我们以包含子视图 A 的视图 V 为例。假设 A 在 V 之外。假设您可以看到 A,然后点击 A。
现在开始命中测试。但事情是这样的:它从 window 的级别开始,并按照视图层次结构 向下 的方式工作。所以 window 首先询问它的子视图;只有一个,视图控制器的主视图。
所以现在我们递归,视图控制器的主视图询问它的个子视图。其中之一是 V。“嘿,V,水龙头在你体内吗?” “不!” (你必须同意这是正确答案,因为我们已经说过 A 在 V 之外。)
所以视图控制器的主视图放弃了 V,并且永远不会发现水龙头在A上,因为我们从来没有递归到那么远。所以它向链上报告:“水龙头不在我的子视图的 any 上,所以我必须报告水龙头在 me 上”点击 通过 进入视图控制器的主视图。
但是您可以通过覆盖 hitTest
:
override func hitTest(_ point: CGPoint, with e: UIEvent?) -> UIView? {
if let result = super.hitTest(point, with:e) {
return result
}
for sub in self.subviews.reversed() {
let pt = self.convert(point, to:sub)
if let result = sub.hitTest(pt, with:e) {
return result
}
}
return nil
}