自定义 UIView 中的 UITapGestureRecognizer 不起作用

UITapGestureRecognizer in custom UIView doesn't work

Swift5/Xcode12.4

我为我的自定义创建了一个 xib 文件 MarkerView - 布局非常简单:

- View
-- StackView
--- DotLabel
--- NameLabel
默认情况下,

ViewStackView 都在检查器中设置为“已启用用户交互”。 DotLabelNameLabel 不是,但勾选它们的框似乎并没有真正改变任何东西。

在运行时,我在我的 ViewController 中创建了 MarkerViews(出于测试目的,只有一个 atm),并将它们添加到已经包含图像(有效)的 ScrollView 中:

override func viewDidAppear(_ animated: Bool) {
    createMarkers()
    setUpMarkers()
}

private func createMarkers() {
    let marker = MarkerView()
    marker.setUp("Some Text")
    markers.append(marker)
    scrollView.addSubview(marker)
    marker.alpha = 0.0
}

private func setUpMarkers() {
    for (i,m) in markers.enumerated() {
        m.frame.origin = CGPoint(x: (i+1)*100,y: (i+1)*100)
        m.alpha = 1.0
    }
}

这些 MarkerViews 应该是可点击的,所以我添加了一个 UITapGestureRecognizer 但链接函数从未被调用。这是我的完整标记视图:

class MarkerView: UIView {
    @IBOutlet weak var stackView: UIStackView!
    @IBOutlet weak var dotLabel: UILabel!
    @IBOutlet weak var nameLabel: UILabel!
    
    let nibName = "MarkerView"
    var contentView:UIView?
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() {
        guard let view = loadViewFromNib() else { return }
        view.frame = self.bounds
        self.addSubview(view)
        contentView = view
        
        let gesture = UITapGestureRecognizer(target: self, action: #selector(self.clickedMarker))
        self.addGestureRecognizer(gesture)
    }
    func loadViewFromNib() -> UIView? {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: nibName, bundle: bundle)
        return nib.instantiate(withOwner: self, options: nil).first as? UIView
    }
    
    func setUp(_ name:String) {
        nameLabel.text = name
        nameLabel.sizeToFit()
        stackView.sizeToFit()
    }
    
    @objc private func clickedMarker() {
        print("Clicked Marker!")
    }
}

其他问题建议在添加手势识别器之前添加self.isUserInteractionEnabled = true。我什至为 StackView 和两个标签都启用了它,还尝试将手势识别器添加到 ScrollView 但 none 它有帮助。

为什么手势识别器不工作,我该如何解决这个问题?

编辑:根据 Sweeper 的建议,我的代码现在如下所示:

class MarkerView: UIView, UIGestureRecognizerDelegate {
    .....
    func commonInit() {
        guard let view = loadViewFromNib() else { return }
        view.frame = self.bounds
        let gesture = UITapGestureRecognizer(target: self, action: #selector(self.clickedMarker))
        gesture.delegate = self
        self.isUserInteractionEnabled = true
        self.addGestureRecognizer(gesture)
        self.addSubview(view)
        contentView = view
    }
    .....
    func gestureRecognizer(_: UIGestureRecognizer, _ otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        print("gestureRecognizer")
        return true
    }
}

“gestureRecognizer”永远不会打印到控制台。

问题@Sweeper linked 已接受答案下方的评论之一给出了很好的提示:

What is the size of the content view (log it)?

print(self.frame.size)
print(contentView.frame.size)

都在我的应用程序中打印了 (0.0, 0.0)UITapGestureRecognizer 附加到 self,因此即使它有效,也根本没有您可以点击的区域来识别水龙头。

解决方法: 设置 UIView 的大小 UITapGestureRecognizer 附加到:

stackView.layoutIfNeeded()
stackView.sizeToFit()
self.layoutIfNeeded()
self.sizeToFit()

对我不起作用,之后尺寸仍然是 (0.0, 0.0)。相反,我直接设置 self 的大小:

self.frame.size = stackView.frame.size

这也设置了 contentView 的大小(因为它是 MarkerView's child) but of course requires the size of stackView to be set properly too. You could add up the childrens' widths/heights(包括 spacing/margins)你自己或简单地调用:

stackView.layoutIfNeeded()
stackView.sizeToFit()

完成代码:

func setUp(_ name:String) {
    nameLabel.text = name
    //nameLabel.sizeToFit() //Not needed anymore
    stackView.layoutIfNeeded() //Makes sure that the labels already use the proper size after updating the text
    stackView.sizeToFit()
    self.frame.size = stackView.frame.size

    let gesture = UITapGestureRecognizer(target: self, action: #selector(self.onClickStackView))
    self.addGestureRecognizer(gesture)
}

细节:

  1. 不需要 delegate 并覆盖 func gestureRecognizer(_: UIGestureRecognizer, _ otherGestureRecognizer: UIGestureRecognizer) -> Bool 以始终 return true,这可能是替代方案,但我没有设法获得该版本工作 - 该功能从未被调用过。如果有人知道该怎么做,请随时 post 它作为替代解决方案。
  2. 附加 UITapGestureRecognizer 必须在父视图知道它的大小后完成,而在 commonInit 中它不知道,因为标签的文本和所有内容的大小都没有设置还没有。可以在识别器已连接后添加更多文本,但如果使用上述代码,超过原始长度(在连接识别器时)的所有内容都将不可点击。
  3. 使用父视图的背景色(stackView 的背景色被忽略)但可以简单地将其设置为透明色。
  4. 将手势识别器附加到 stackView 而不是 self 的工作方式相同。您甚至可以使用 UILabel,但默认情况下它们的“已启用用户交互”是关闭的 - 首先在“属性检查器”(“视图”部分中的复选框)或代码(myLabel.isUserInteractionEnabled = true).