简体   繁体   English

UILabel 在以编程方式创建的堆栈视图中不可单击 Swift

[英]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:我尝试了两种不同的解决方案:

  1. 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....然后在第二种方式下面....

  1. 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.它应该工作正常。

  1. Read documentation: UITapGestureRecognizer is a class. It means that after calling function all internal variables will be destroyed.阅读文档:UITapGestureRecognizer 是一个 class。这意味着在调用 function 之后,所有内部变量都将被销毁。 So just move 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
    }()
    
    } 
  1. Why are you decided to use Label instead of UIButton (with transparence background color and border line)?为什么您决定使用 Label 而不是 UIButton(具有透明背景颜色和边框线)?
  2. Also you can use UITableView instead of stack & labels您也可以使用 UITableView 而不是堆栈和标签
  3. Maybe this documentation will help too (it is written that usually in one view better to keep one gesture recognizer): https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/coordinating_multiple_gesture_recognizers也许这个文档也会有所帮助(它被写成通常在一个视图中更好地保留一个手势识别器): https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/coordinating_multiple_gesture_recognizers

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM