为啥嵌套视图中的 UILabel 不接收触摸事件/如何测试响应者链?
Posted
技术标签:
【中文标题】为啥嵌套视图中的 UILabel 不接收触摸事件/如何测试响应者链?【英文标题】:Why doesn't my UILabel in a nested view receive touch events / How can I test the Responder Chain?为什么嵌套视图中的 UILabel 不接收触摸事件/如何测试响应者链? 【发布时间】:2020-09-09 15:00:43 【问题描述】:我发现了很多关于不接收触摸事件的类似问题,并且我知道在某些情况下,可能需要编写自定义的 hitTest 函数 - 但我还读到响应器链将遍历层次结构中的视图和视图控制器- 我不明白为什么我的实现需要自定义 hitTest。
我正在寻找解释和/或指向说明如何测试响应者链的文档的链接。 Xcode 10.2.1 中出现此问题。
我的场景(我没有使用 Storyboard):
我有一个 mainViewController,它提供了一个带有 ImageView 和一些标签的全屏视图。我已将 TapGestureRecognizers 附加到 ImageView 和其中一个标签 - 它们都可以正常工作。 当我点击标签时,我添加了一个子视图控制器,它作为一个子视图添加到主视图控制器。视图被限制为仅覆盖屏幕的右半部分。 子 viewController 包含一个垂直堆栈视图,其中包含 3 个排列的子视图。 每个排列的Subview 都包含一个 Label 和一个水平 StackView。 每个水平stackView都包含一个带有标签的View作为子视图。 子视图中的 Label 将其 isUserInteractionEnabled 标志设置为 True 并添加 TapGestureRecognizer。 这些是子 ViewController 中唯一设置了“isUserInteractionEnabled”的对象。标签嵌套得相当深,但由于这是直接的父/子层次结构(与属于 NavigationController 的 2 个视图相反),我希望标签位于正常的响应者链中并正常运行。 Stack View 会改变这种行为吗?我是否需要在某些视图上将“isUserInteractionEnabled”值显式设置为 False?有没有办法可以将日志记录添加到 ResponderChain,以便我可以查看它检查了哪些视图并找出它被阻止的位置?
阅读this *** post 后,我尝试在 viewDidLayoutSubviews() 中添加我的手势识别器,而不是如下所示 - 但它们仍然没有收到点击事件。
提前感谢任何可以提供建议或帮助的人。
这是不响应我的点击事件的标签的代码以及它应该调用的点击事件:
func makeColorItem(colorName:String, bgColor:UIColor, fgColor:UIColor) -> UIView
let colorNumber:Int = colorLabelDict.count
let colorView:UIView =
let v = UIView()
v.tag = 700 + colorNumber
v.backgroundColor = .clear
v.contentMode = .center
return v
()
self.view.addSubview(colorView)
let tapColorGR:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapColor))
let colorChoice: UILabel =
let l = UILabel()
l.tag = 700 + colorNumber
l.isUserInteractionEnabled = true
l.addGestureRecognizer(tapColorGR)
l.text = colorName
l.textAlignment = .center
l.textColor = fgColor
l.backgroundColor = bgColor
l.font = UIFont.systemFont(ofSize: 24, weight: .bold)
l.layer.borderColor = fgColor.cgColor
l.layer.borderWidth = 1
l.layer.cornerRadius = 20
l.layer.masksToBounds = true
l.adjustsFontSizeToFitWidth = true
l.translatesAutoresizingMaskIntoConstraints = false
l.widthAnchor.constraint(equalToConstant: 100)
return l
()
colorView.addSubview(colorChoice)
colorChoice.centerXAnchor.constraint(equalTo: colorView.centerXAnchor).isActive = true
colorChoice.centerYAnchor.constraint(equalTo: colorView.centerYAnchor).isActive = true
colorChoice.heightAnchor.constraint(equalToConstant: 50).isActive = true
colorChoice.widthAnchor.constraint(equalToConstant: 100).isActive = true
colorLabelDict[colorNumber] = colorChoice
return colorView
@objc func tapColor(sender:UITapGestureRecognizer)
print("A Color was tapped...with tag:\(sender.view?.tag ?? -1)")
if let cn = sender.view?.tag
colorNumber = cn
let v = colorLabelDict[cn]
if let l = (v?.subviews.first as? UILabel)
print("The \(l.text) label was tapped.")
【问题讨论】:
如果您提供更多代码会有所帮助 - 足以说明发生了什么。它看起来您正在添加UILabel
对colorLabelDict
的引用...正在尝试获取第一个子视图?
你是对的。这是我尝试将 tapGestureRecognizer 添加到视图而不是标签时的剩余代码。在这种情况下,sender.view 是 UIView 而 Label 是它的第一个子视图。感谢您指出了这一点。一旦我终于能够触发点击事件,我会更正它。
【参考方案1】:
看起来您没有获得点击识别的主要原因是因为您添加了UILabel
作为UIView
的子视图,但您没有给UIView
任何约束。所以视图最终的宽度和高度为零,并且标签存在于视图的边界之外。
没有看到你所有的代码,看起来你不需要额外的视图来保存标签。
看看这个...它将向主视图添加一个垂直堆栈视图 - 以 X 和 Y 为中心 - 并将“colorChoice”标签添加到堆栈视图:
class TestViewController: UIViewController
let stack: UIStackView =
let v = UIStackView()
v.axis = .vertical
v.spacing = 4
return v
()
var colorLabelDict: [Int: UIView] = [:]
override func viewDidLoad()
super.viewDidLoad()
let v1 = makeColorLabel(colorName: "red", bgColor: .red, fgColor: .white)
let v2 = makeColorLabel(colorName: "green", bgColor: .green, fgColor: .black)
let v3 = makeColorLabel(colorName: "blue", bgColor: .blue, fgColor: .white)
[v1, v2, v3].forEach
stack.addArrangedSubview($0)
stack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stack)
NSLayoutConstraint.activate([
stack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stack.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
func makeColorLabel(colorName:String, bgColor:UIColor, fgColor:UIColor) -> UILabel
let colorNumber:Int = colorLabelDict.count
// create tap gesture recognizer
let tapColorGR:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapColor))
let colorChoice: UILabel =
let l = UILabel()
l.tag = 700 + colorNumber
l.addGestureRecognizer(tapColorGR)
l.text = colorName
l.textAlignment = .center
l.textColor = fgColor
l.backgroundColor = bgColor
l.font = UIFont.systemFont(ofSize: 24, weight: .bold)
l.layer.borderColor = fgColor.cgColor
l.layer.borderWidth = 1
l.layer.cornerRadius = 20
l.layer.masksToBounds = true
l.adjustsFontSizeToFitWidth = true
l.translatesAutoresizingMaskIntoConstraints = false
// default .isUserInteractionEnabled for UILabel is false, so enable it
l.isUserInteractionEnabled = true
return l
()
NSLayoutConstraint.activate([
// label height: 50, width: 100
colorChoice.heightAnchor.constraint(equalToConstant: 50),
colorChoice.widthAnchor.constraint(equalToConstant: 100),
])
// assign reference to this label in colorLabelDict dictionary
colorLabelDict[colorNumber] = colorChoice
// return newly created label
return colorChoice
@objc func tapColor(sender:UITapGestureRecognizer)
print("A Color was tapped...with tag:\(sender.view?.tag ?? -1)")
// unwrap the view that was tapped, make sure it's a UILabel
guard let tappedView = sender.view as? UILabel else
return
let cn = tappedView.tag
let colorNumber = cn
print("The \(tappedView.text ?? "No text") label was tapped.")
运行结果:
这些是 3 个UILabel
s,点击每个会触发tapColor()
函数,并将其打印到调试控制台:
A Color was tapped...with tag:700
The red label was tapped.
A Color was tapped...with tag:701
The green label was tapped.
A Color was tapped...with tag:702
The blue label was tapped.
【讨论】:
最初,我尝试只将标签添加到堆栈视图,但堆栈视图操纵了它们的大小(使用 .fillEqually)。这就是为什么我将它们添加到 UIView 并将它们限制在其中 - 认为 stackView 可以根据需要操纵 UIView 大小,并且我的标签仍然可以正确显示。 @Jim - 好吧,这取决于你想要做什么。一般来说,要么给排列的子视图大小约束,让它们改变堆栈视图的大小,要么给堆栈视图约束,让它管理排列的子视图的大小。没有看到你的实际目标,很难说你应该走哪条路。 我有 7 个颜色标签,希望它们在一条水平线上以相同的大小显示。它们应该看起来与您提供的输出非常相似 - 但在它们被限制的视图中水平且等距分布。我承认 - 我刚刚开始使用 StackViews 并没有完全理解它们。通常需要对分布值进行大量试验和错误才能最终使其看起来正确。让我尝试在我的容器 UIView 中添加一个框架(所以它不是零),看看它是否可以工作。 @Jim - 在您发布的代码中,您将每个标签宽度设置为100
...这显然不适合半个屏幕。展示您真正想做的事情的图片。
该应用程序被设计为(目前无论如何)仅在 iPad 上运行,仅在横向上运行,当我说 1/2 屏幕时我概括了 - 实际上大约是 2/3 - 所以它确实适合。我将接受您的正确回答,因为我感谢您的宝贵时间,它确实帮助我纠正了问题。就像你说的那样 - 持有标签的 textView 的宽度为 0。我添加了一个 widthConstraint ,它现在可以工作了。非常感谢。以上是关于为啥嵌套视图中的 UILabel 不接收触摸事件/如何测试响应者链?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 UIView 的这些自动布局约束会阻止 UIButton 子视图接收触摸事件?
如何告诉带有 UITextField 的自定义 UIView 接收触摸事件
以 UIScrollView 作为子类的 UIView 不接收触摸事件