[英]UILabel not clickable in stack view programmatically created Swift
My question and code is based on this answer to one of my previous questions.我的问题和代码基于对我之前的一个问题的回答。 I have programmatically created stackview where several labels are stored and I'm trying to make these labels clickable.我以编程方式创建了堆栈视图,其中存储了多个标签,我正在尝试使这些标签可点击。 I tried two different solutions:我尝试了两种不同的解决方案:
Make clickable label. I created function and assigned it to the label in the gesture recognizer:使 label 可点击。我创建了 function 并将其分配给手势识别器中的 label:
public func setTapListener(_ label: UILabel){ let tapGesture: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapGestureMethod(_:))) tapGesture.numberOfTapsRequired = 1 tapGesture.numberOfTouchesRequired = 1 label.isUserInteractionEnabled = true label.addGestureRecognizer(tapGesture) } @objc func tapGestureMethod(_ gesture: UITapGestureRecognizer) { print(gesture.view?.tag) }
but it does not work.但它不起作用。 Then below the second way....然后在第二种方式下面....
I thought that maybe the 1st way does not work because the labels are in UIStackView so I decided to assign click listener to the stack view and then determine on which view we clicked.我想也许第一种方法不起作用,因为标签在 UIStackView 中,所以我决定将点击侦听器分配给堆栈视图,然后确定我们点击了哪个视图。 At first I assigned to each of labels in the stackview tag and listened to clicks:起初我分配给 stackview 标签中的每个标签并听取点击:
let tap = UITapGestureRecognizer(target: self, action: #selector(didTapCard(sender:))) labelsStack.addGestureRecognizer(tap).... @objc func didTapCard (sender: UITapGestureRecognizer) { (sender.view as? UIStackView)?.arrangedSubviews.forEach({ label in print((label as. UILabel).text) }) }
but the problem is that the click listener works only on the part of the stack view and when I tried to determine on which view we clicked it was not possible.但问题是点击侦听器仅适用于堆栈视图的一部分,当我试图确定我们点击了哪个视图时,这是不可能的。
I think that possibly the problem is with that I tried to assign one click listener to several views, but not sure that works as I thought.我认为问题可能出在我试图将一个点击监听器分配给多个视图,但不确定它是否像我想的那样工作。 I'm trying to make each label in the stackview clickable, but after click I will only need getting text from the label, so that is why I used one click listener for all views.我试图让堆栈视图中的每个 label 都可以点击,但点击后我只需要从 label 获取文本,所以这就是为什么我对所有视图使用一个点击监听器。
Applying a transform
to a view (button, label, view, etc) changes the visual appearance , not the structure.将transform
应用于视图(按钮、label、视图等)会更改视觉外观,而不是结构。
Because you're working with rotated views, you need to implement hit-testing.因为您正在使用旋转视图,所以您需要实施命中测试。
Quick example:快速示例:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// convert the point to the labels stack view coordinate space
let pt = labelsStack.convert(point, from: self)
// loop through arranged subviews
for i in 0..<labelsStack.arrangedSubviews.count {
let v = labelsStack.arrangedSubviews[i]
// if converted point is inside subview
if v.frame.contains(pt) {
return v
}
}
return super.hitTest(point, with: event)
}
Assuming you're still working with the MyCustomView
class and layout from your previous questions, we'll build on that with a few changes for layout, and to allow tapping the labels.假设您仍在使用MyCustomView
class 和您之前问题中的布局,我们将在此基础上对布局进行一些更改,并允许点击标签。
Complete example:完整示例:
class Step5VC: UIViewController {
// create the custom "left-side" view
let myView = MyCustomView()
// create the "main" stack view
let mainStackView = UIStackView()
// create the "bottom labels" stack view
let bottomLabelsStack = UIStackView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemYellow
guard let img = UIImage(named: "pro1") else {
fatalError("Need an image!")
}
// create the image view
let imgView = UIImageView()
imgView.contentMode = .scaleToFill
imgView.image = img
mainStackView.axis = .horizontal
bottomLabelsStack.axis = .horizontal
bottomLabelsStack.distribution = .fillEqually
// add views to the main stack view
mainStackView.addArrangedSubview(myView)
mainStackView.addArrangedSubview(imgView)
// add main stack view and bottom labels stack view to view
mainStackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(mainStackView)
bottomLabelsStack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(bottomLabelsStack)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain Top/Leading/Trailing
mainStackView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
mainStackView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
//mainStackView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
// we want the image view to be 270 x 270
imgView.widthAnchor.constraint(equalToConstant: 270.0),
imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor),
// constrain the bottom lables to the bottom of the main stack view
// same width as the image view
// aligned trailing
bottomLabelsStack.topAnchor.constraint(equalTo: mainStackView.bottomAnchor),
bottomLabelsStack.trailingAnchor.constraint(equalTo: mainStackView.trailingAnchor),
bottomLabelsStack.widthAnchor.constraint(equalTo: imgView.widthAnchor),
])
// setup the left-side custom view
myView.titleText = "Gefährdung"
let titles: [String] = [
"keine / gering", "mittlere", "erhöhte", "hohe",
]
let colors: [UIColor] = [
UIColor(red: 0.863, green: 0.894, blue: 0.527, alpha: 1.0),
UIColor(red: 0.942, green: 0.956, blue: 0.767, alpha: 1.0),
UIColor(red: 0.728, green: 0.828, blue: 0.838, alpha: 1.0),
UIColor(red: 0.499, green: 0.706, blue: 0.739, alpha: 1.0),
]
for (c, t) in zip(colors, titles) {
// because we'll be using hitTest in our Custom View
// we don't need to set .isUserInteractionEnabled = true
// create a "color label"
let cl = colorLabel(withColor: c, title: t, titleColor: .black)
// we're limiting the height to 270, so
// let's use a smaller font for the left-side labels
cl.font = .systemFont(ofSize: 12.0, weight: .light)
// create a tap recognizer
let t = UITapGestureRecognizer(target: self, action: #selector(didTapRotatedLeftLabel(_:)))
// add the recognizer to the label
cl.addGestureRecognizer(t)
// add the label to the custom myView
myView.addLabel(cl)
}
// rotate the left-side custom view 90-degrees counter-clockwise
myView.rotateTo(-.pi * 0.5)
// setup the bottom labels
let colorDictionary = [
"Red":UIColor.systemRed,
"Green":UIColor.systemGreen,
"Blue":UIColor.systemBlue,
]
for (myKey,myValue) in colorDictionary {
// bottom labels are not rotated, so we can add tap gesture recognizer directly
// create a "color label"
let cl = colorLabel(withColor: myValue, title: myKey, titleColor: .white)
// let's use a smaller, bold font for the left-side labels
cl.font = .systemFont(ofSize: 12.0, weight: .bold)
// by default, .isUserInteractionEnabled is False for UILabel
// so we must set .isUserInteractionEnabled = true
cl.isUserInteractionEnabled = true
// create a tap recognizer
let t = UITapGestureRecognizer(target: self, action: #selector(didTapBottomLabel(_:)))
// add the recognizer to the label
cl.addGestureRecognizer(t)
bottomLabelsStack.addArrangedSubview(cl)
}
}
@objc func didTapRotatedLeftLabel (_ sender: UITapGestureRecognizer) {
if let v = sender.view as? UILabel {
let title = v.text ?? "label with no text"
print("Tapped Label in Rotated Custom View:", title)
// do something based on the tapped label/view
}
}
@objc func didTapBottomLabel (_ sender: UITapGestureRecognizer) {
if let v = sender.view as? UILabel {
let title = v.text ?? "label with no text"
print("Tapped Bottom Label:", title)
// do something based on the tapped label/view
}
}
func colorLabel(withColor color:UIColor, title:String, titleColor:UIColor) -> UILabel {
let newLabel = PaddedLabel()
newLabel.padding = UIEdgeInsets(top: 6, left: 8, bottom: 6, right: 8)
newLabel.backgroundColor = color
newLabel.text = title
newLabel.textAlignment = .center
newLabel.textColor = titleColor
newLabel.setContentHuggingPriority(.required, for: .vertical)
return newLabel
}
}
class MyCustomView: UIView {
public var titleText: String = "" {
didSet { titleLabel.text = titleText }
}
public func addLabel(_ v: UIView) {
labelsStack.addArrangedSubview(v)
}
public func rotateTo(_ d: Double) {
// get the container view (in this case, it's the outer stack view)
if let v = subviews.first {
// set the rotation transform
if d == 0 {
self.transform = .identity
} else {
self.transform = CGAffineTransform(rotationAngle: d)
}
// remove the container view
v.removeFromSuperview()
// tell it to layout itself
v.setNeedsLayout()
v.layoutIfNeeded()
// get the frame of the container view
// apply the same transform as self
let r = v.frame.applying(self.transform)
wC.isActive = false
hC.isActive = false
// add it back
addSubview(v)
// set self's width and height anchors
// to the width and height of the container
wC = self.widthAnchor.constraint(equalToConstant: r.width)
hC = self.heightAnchor.constraint(equalToConstant: r.height)
guard let sv = v.superview else {
fatalError("no superview")
}
// apply the new constraints
NSLayoutConstraint.activate([
v.centerXAnchor.constraint(equalTo: self.centerXAnchor),
v.centerYAnchor.constraint(equalTo: self.centerYAnchor),
wC,
outerStack.widthAnchor.constraint(equalTo: sv.heightAnchor),
])
}
}
// our subviews
private let outerStack = UIStackView()
private let titleLabel = UILabel()
private let labelsStack = UIStackView()
private var wC: NSLayoutConstraint!
private var hC: NSLayoutConstraint!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
// stack views and label properties
outerStack.axis = .vertical
outerStack.distribution = .fillEqually
labelsStack.axis = .horizontal
// let's use .fillProportionally to help fit the labels
labelsStack.distribution = .fillProportionally
titleLabel.textAlignment = .center
titleLabel.backgroundColor = .lightGray
titleLabel.textColor = .white
// add title label and labels stack to outer stack
outerStack.addArrangedSubview(titleLabel)
outerStack.addArrangedSubview(labelsStack)
outerStack.translatesAutoresizingMaskIntoConstraints = false
addSubview(outerStack)
wC = self.widthAnchor.constraint(equalTo: outerStack.widthAnchor)
hC = self.heightAnchor.constraint(equalTo: outerStack.heightAnchor)
NSLayoutConstraint.activate([
outerStack.centerXAnchor.constraint(equalTo: self.centerXAnchor),
outerStack.centerYAnchor.constraint(equalTo: self.centerYAnchor),
wC, hC,
])
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// convert the point to the labels stack view coordinate space
let pt = labelsStack.convert(point, from: self)
// loop through arranged subviews
for i in 0..<labelsStack.arrangedSubviews.count {
let v = labelsStack.arrangedSubviews[i]
// if converted point is inside subview
if v.frame.contains(pt) {
return v
}
}
return super.hitTest(point, with: event)
}
}
class PaddedLabel: UILabel {
var padding: UIEdgeInsets = .zero
override func drawText(in rect: CGRect) {
super.drawText(in: rect.inset(by: padding))
}
override var intrinsicContentSize : CGSize {
let sz = super.intrinsicContentSize
return CGSize(width: sz.width + padding.left + padding.right, height: sz.height + padding.top + padding.bottom)
}
}
I have written the same code as yours and it is working fine.我编写了与您相同的代码,并且运行良好。 Check the console output in the attached screenshot.检查附件屏幕截图中的控制台 output。
Code is as follows:代码如下:
override func viewDidLoad() {
super.viewDidLoad()
let stackView = UIStackView()
stackView.axis = .vertical
stackView.distribution = .fill
stackView.alignment = .top
stackView.spacing = 4
let label1 = UILabel()
label1.numberOfLines = 0
label1.text = "Row 1"
label1.tag = 11
setTapListener(label1)
stackView.addArrangedSubview(label1)
let label2 = UILabel()
label2.numberOfLines = 0
label2.text = "Row 2"
label2.tag = 22
setTapListener(label2)
stackView.addArrangedSubview(label2)
stackView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(stackView)
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 300).isActive = true
}
public func setTapListener(_ label: UILabel){
let tapGesture: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapGestureMethod(_:)))
tapGesture.numberOfTapsRequired = 1
tapGesture.numberOfTouchesRequired = 1
label.isUserInteractionEnabled = true
label.addGestureRecognizer(tapGesture)
}
@objc func tapGestureMethod(_ gesture: UITapGestureRecognizer) {
print(gesture.view?.tag ?? 0)
}
So there is no problem in the code, the problem may be in the UI.所以代码没有问题,问题可能出在UI上。 The UI may not be refreshed or some other element may be overlapping over it. UI 可能不会刷新,或者某些其他元素可能会重叠在它上面。
1- Try to use the view debugger to check whether any view or element is overlapping over it or not. 1- 尝试使用视图调试器检查是否有任何视图或元素与其重叠。
2- Try to refresh the layout using layoutSubviews to check whether issue persists or not. 2- 尝试使用 layoutSubviews 刷新布局以检查问题是否仍然存在。
Try to debug the issue with these techniques and solve if you find any.尝试使用这些技术调试问题并在发现问题时解决。 It should work fine.它应该工作正常。
let tapGesture:
into the class above, do not create it in this function. For example:所以只要把let tapGesture:
移到上面的class中即可,不要在这个function中创建。例如: class ScrollView: UIScrollView {
let tapGesture = {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapGestureMethod(_:)))
tapGesture.numberOfTapsRequired = 1
tapGesture.numberOfTouchesRequired = 1
label.isUserInteractionEnabled = true
label.addGestureRecognizer(tapGesture)
return tapGesture
}()
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.