簡體   English   中英

UILabel 在以編程方式創建的堆棧視圖中不可單擊 Swift

[英]UILabel not clickable in stack view programmatically created Swift

我的問題和代碼基於對我之前的一個問題的回答 我以編程方式創建了堆棧視圖,其中存儲了多個標簽,我正在嘗試使這些標簽可點擊。 我嘗試了兩種不同的解決方案:

  1. 使 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) }

但它不起作用。 然后在第二種方式下面....

  1. 我想也許第一種方法不起作用,因為標簽在 UIStackView 中,所以我決定將點擊偵聽器分配給堆棧視圖,然后確定我們點擊了哪個視圖。 起初我分配給 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) }) }

但問題是點擊偵聽器僅適用於堆棧視圖的一部分,當我試圖確定我們點擊了哪個視圖時,這是不可能的。

我認為問題可能出在我試圖將一個點擊監聽器分配給多個視圖,但不確定它是否像我想的那樣工作。 我試圖讓堆棧視圖中的每個 label 都可以點擊,但點擊后我只需要從 label 獲取文本,所以這就是為什么我對所有視圖使用一個點擊監聽器。

transform應用於視圖(按鈕、label、視圖等)會更改視覺外觀,而不是結構。

因為您正在使用旋轉視圖,所以您需要實施命中測試。

快速示例:

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)
    
}

假設您仍在使用MyCustomView class 和您之前問題中的布局,我們將在此基礎上對布局進行一些更改,並允許點擊標簽。

完整示例:

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)
    }
}

我編寫了與您相同的代碼,並且運行良好。 檢查附件屏幕截圖中的控制台 output。

在此處輸入圖像描述

代碼如下:

 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)
}

所以代碼沒有問題,問題可能出在UI上。 UI 可能不會刷新,或者某些其他元素可能會重疊在它上面。

1- 嘗試使用視圖調試器檢查是否有任何視圖或元素與其重疊。

2- 嘗試使用 layoutSubviews 刷新布局以檢查問題是否仍然存在。

嘗試使用這些技術調試問題並在發現問題時解決。 它應該工作正常。

  1. 閱讀文檔:UITapGestureRecognizer 是一個 class。這意味着在調用 function 之后,所有內部變量都將被銷毀。 所以只要把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. 為什么您決定使用 Label 而不是 UIButton(具有透明背景顏色和邊框線)?
  2. 您也可以使用 UITableView 而不是堆棧和標簽
  3. 也許這個文檔也會有所幫助(它被寫成通常在一個視圖中更好地保留一個手勢識別器): 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