自定义 UIView 中的 UITapGestureRecognizer 不起作用
UITapGestureRecognizer in custom UIView doesn't work
Swift5/Xcode12.4
我为我的自定义创建了一个 xib 文件 MarkerView
- 布局非常简单:
- View
-- StackView
--- DotLabel
--- NameLabel
默认情况下,View
和 StackView
都在检查器中设置为“已启用用户交互”。 DotLabel
和 NameLabel
不是,但勾选它们的框似乎并没有真正改变任何东西。
在运行时,我在我的 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)
}
细节:
- 不需要
delegate
并覆盖 func gestureRecognizer(_: UIGestureRecognizer, _ otherGestureRecognizer: UIGestureRecognizer) -> Bool
以始终 return true
,这可能是替代方案,但我没有设法获得该版本工作 - 该功能从未被调用过。如果有人知道该怎么做,请随时 post 它作为替代解决方案。
- 附加
UITapGestureRecognizer
必须在父视图知道它的大小后完成,而在 commonInit
中它不知道,因为标签的文本和所有内容的大小都没有设置还没有。可以在识别器已连接后添加更多文本,但如果使用上述代码,超过原始长度(在连接识别器时)的所有内容都将不可点击。
- 使用父视图的背景色(
stackView
的背景色被忽略)但可以简单地将其设置为透明色。
- 将手势识别器附加到
stackView
而不是 self
的工作方式相同。您甚至可以使用 UILabel
,但默认情况下它们的“已启用用户交互”是关闭的 - 首先在“属性检查器”(“视图”部分中的复选框)或代码(myLabel.isUserInteractionEnabled = true
).
Swift5/Xcode12.4
我为我的自定义创建了一个 xib 文件 MarkerView
- 布局非常简单:
- View
-- StackView
--- DotLabel
--- NameLabel
默认情况下,View
和 StackView
都在检查器中设置为“已启用用户交互”。 DotLabel
和 NameLabel
不是,但勾选它们的框似乎并没有真正改变任何东西。
在运行时,我在我的 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)
}
细节:
- 不需要
delegate
并覆盖func gestureRecognizer(_: UIGestureRecognizer, _ otherGestureRecognizer: UIGestureRecognizer) -> Bool
以始终 returntrue
,这可能是替代方案,但我没有设法获得该版本工作 - 该功能从未被调用过。如果有人知道该怎么做,请随时 post 它作为替代解决方案。 - 附加
UITapGestureRecognizer
必须在父视图知道它的大小后完成,而在commonInit
中它不知道,因为标签的文本和所有内容的大小都没有设置还没有。可以在识别器已连接后添加更多文本,但如果使用上述代码,超过原始长度(在连接识别器时)的所有内容都将不可点击。 - 使用父视图的背景色(
stackView
的背景色被忽略)但可以简单地将其设置为透明色。 - 将手势识别器附加到
stackView
而不是self
的工作方式相同。您甚至可以使用UILabel
,但默认情况下它们的“已启用用户交互”是关闭的 - 首先在“属性检查器”(“视图”部分中的复选框)或代码(myLabel.isUserInteractionEnabled = true
).