如何在循环中创建的视图上处理 UITapGestureRecognizer?

Posted

技术标签:

【中文标题】如何在循环中创建的视图上处理 UITapGestureRecognizer?【英文标题】:How to handle UITapGestureRecognizer on views created in loops? 【发布时间】:2020-12-22 23:05:13 【问题描述】:

我有一个垂直的 UIStackView,我在这个视图中附加了一个水平的 UIStackView 和一些 UILabel。我附加了 UITapGestureRecognizer 来处理点击动作。当我点击一行时,如何更改例如标签(这是一个子视图)?

到目前为止,这是我的代码:

for article in articleType 
    //Create a row
    let row = UIStackView()
    row.axis = NSLayoutConstraint.Axis.horizontal
    
    //Create a label and indicator
    let label = UILabel()
    label.text = article.label

    let indicator = UILabel()
    indicator.text = "off"

    //Add label to row and row to parent stack view
    row.addSubView(label)
    row.addSubView(indicator)
    mainUiStackView.addArrangedSubview(row)

    //Create tap handled
    let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(sender:)))
    row.addGestureRecognizer(tap)


@objc func handleTap(sender: UITapGestureRecognizer? = nil) 
    print(sender.view)

    //This is where i'm stuck, how can i change the second label's text to "on" for example?
    sender.view.indicator.text = "on"


【问题讨论】:

您不能点击堆栈视图。 @matt,为什么不呢?我刚刚创建了一个测试应用程序,在其中我将轻击手势识别器添加到排列的子视图和堆栈视图中,它们都按预期触发。 (我必须点击子视图之间的空间才能触发堆栈视图的点击手势识别器,但它起作用了。) @DuncanC 好吧,也许他们改变了这一点。过去,堆栈视图是一种特殊的视图。 所以每个堆栈视图将始终包含恰好 2 个标签,当用户点击给定堆栈视图时,您总是想进入堆栈视图并将其标签文本设置为“on”? 是的。我实际上想将复选框图像更改为开/关,但在示例代码中使用了 UILabel 所以它并不复杂,但逻辑将以相同的方式应用。 【参考方案1】:

您的代码会循环并在局部变量row 中创建一系列堆栈视图。然后,您不会将这些堆栈视图添加到您的视图层次结构中,因此它们会“掉在地上”。

对于这一行:

let row = UIStackView()

每次通过您的 for 循环后,您创建的堆栈视图将超出范围并被释放。

编辑:

在您指出之后,我现在看到您将row 堆栈视图添加到mainUiStackView 的位置。

我根据您创建的 cmets 收集到,您希望在您的“行”堆栈视图之一上进行点击,将该堆栈视图中的 indicator 标签设置为“打开”。

我建议构建一个包含行堆栈视图及其标签视图(作为结构)的数组。然后找到被点击的行并将其标签设置为开启状态:

struct RowStackViewStruct  
   let stackView: UIStackView
   let label: UILabel


var rowStackViews = [RowStackViewStruct]()


for article in articleType 
    //Create a row
    let row = UIStackView()
    row.axis = NSLayoutConstraint.Axis.horizontal
    
    //Create a label and indicator
    let label = UILabel()
    label.text = article.label

    let indicator = UILabel()
    indicator.text = "off"

    //Add label to row and row to parent stack view
    row.addSubView(label)
    row.addSubView(indicator)
    mainUiStackView.addArrangedSubview(row)

    //Create tap handled
    let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(sender:)))
    row.addGestureRecognizer(tap)

    //Build a RowStackViewStruct for this row
    let newRowStackViewStruct = RowStackViewStruct(stackView: row,
      label: indicator)
    rowStackViews.append(newRowStackViewStruct)


And then in your tap handler code:

@objc func handleTap(sender: UITapGestureRecognizer? = nil) 
    print(sender.view)

    if let aStruct = rowStackViews.first(where:  sender?.view == $0.stackView ) 
       aStruct.label.text = "on"

我在 SO 编辑器中敲出了上面的代码。它可能包含轻微的语法错误,仅供参考,而不是您可以放入的复制粘贴代码。

您似乎真的应该跟踪行的开关状态。看起来你应该有一个模型对象数组来代表你的行的状态,并使用它来构建和维护你的主垂直堆栈视图。事实上,切换到表格视图或集合视图可能会更好,因为它们是由模型驱动的。

【讨论】:

嗨!这部分代码实际上工作正常。我遇到的问题是如何在点击整行后访问特定的 UILabel 子视图。所以 sender.view.indicator.text 显然不起作用,但我不知道如何在没有 IBOutlet 的情况下访问这个开/关标签(我没有,因为我正在创建这些标签在代码循环中) 您发布的代码不完整,您对您想要做什么的描述也是如此。由于我在回答中指出的原因,您发布的代码不起作用。如果您试图点击堆栈视图,找出堆栈视图中的哪个视图被点击,请编辑您的问题以说明这一点,并清楚地说出来。必须回答 20 个问题才能弄清楚别人在问什么,这真是令人抓狂。 对不起,我不确定你的意思。我将整行添加到这一行中的另一个 UIStackView:mainUiStackView.addArrangedSubview(row)。点击操作在行上运行良好, print(sender.view) 正确地分别记录每行的操作。我不想在行内的 UILabels 中添加点击手势,只是在主行中。当点击主行时,我想更改该行内的标签文本之一。这是我在问题中描述的,我不知道如何使它更清楚。 我错过了那条线。请参阅我的答案的编辑。 谢谢,完美运行,只需更改一件事,更新您的答案。【参考方案2】:

首先,确保您要点击的任何标签的isUserInteractionEnabled 属性设置为true,因为它默认设置为false

@objc func handleTap(_ sender: UITapGestureRecognizer) 
    guard let tappedView = sender.view else  return 
    let location = sender.location(in: tappedView.superview)
    if case let hitView as UILabel = tappedView.hitTest(location, with: nil) 
        hitView.text = hitView.text == "off" ? "on" : "off"
    

【讨论】:

对不起,我想我的问题并不清楚。所以我希望点击动作在整行中运行。当我点击行(无论如何,而不是特定的子视图)时,我想将子视图的 UILabel 值从关闭更改为打开。因此,如果我有第二个 UILabel 的 IBOutlet,这将很容易,但是由于我从代码和循环中生成它们,因此不确定如何在 handleTap 函数中访问这些标签。

以上是关于如何在循环中创建的视图上处理 UITapGestureRecognizer?的主要内容,如果未能解决你的问题,请参考以下文章

如何删除在 for 循环中创建的注释

循环遍历Android视图的所有子视图?

如何在不使用 foreach 循环的情况下从视图访问模型

for循环和mysql节点如何等待所有查询完成处理视图

如何使用 SwiftUI 在模态视图中创建 NSManagedObject?

Swift - 如何实现默认视图控制器循环