【问题标题】:Number of Lines for UILabel not Updating as Expected When TableViewCell is Tapped轻按 TableViewCell 时 UILabel 的行数未按预期更新
【发布时间】:2025-11-22 18:20:04
【问题描述】:

当点击单元格时,我正在更改位于自定义 UITableViewCell 中的标签上的 numberOfLines 属性。但是,直到第二次点击,这才会反映在 UI 中。该单元格被配置为表格视图中的原型单元格,最初有 2 行。

有趣的是,当我在 tapped() 函数运行之前和之后打印出 numberOfLines 值时,值开始不同,然后同步 - 在第一次点击之后,我在函数运行之前看到 2 行,然后函数运行后的 0 行。但是,在随后的点击之后,我在函数之前和之后看到相同的值,这使得它看起来好像没有做任何事情,即使 UI 确实拉伸和收缩了单元格,并且下次更改了 numberOfLinesdidSelectRowAtIndexPath 函数运行。

我只看到tableView.reloadRows() 的这种行为。如果我使用tableView.reloadData() 进行完整更新,则单元格在第一次被点击时会适当地增长和折叠。但是,这感觉有点笨拙,并且不像 reloadRows() 那样很好地动画化。

TableView 实现

    func tableView(_ tableView: UITableView, 
                       didSelectRowAt indexPath: IndexPath) {
         guard let cell = tableView.cellForRow(at: indexPath) as? ReviewTableViewCell
             else { return }
         let data = tableData[indexPath.row]

         print("old number of lines: \(cell.detailLabel.numberOfLines)")

             //data.isOpen is set to false initially
             cell.tapped(data.isOpen)
         tableData[indexPath.row].isOpen = !data.isOpen
             tableView.reloadRows(at: [indexPath], with: .fade)

             print("old number of lines: \(cell.detailLabel.numberOfLines)")


//      tableView.reloadData()
    }

自定义表格视图单元格方法

    func tapped(_ isOpen: Bool) {
         if !isOpen {
              detailLabel.numberOfLines = 0     }
         else {
          detailLabel.numberOfLines = 2     }
    }

如果numberOfLines 设置为0,我希望此代码在重新加载tableView.reloadRows() 后展开单元格,并在设置为2 时折叠单元格。这确实有效,但只有在点击单元格之后两次以上。这应该也适用于第一次点击。

这是一个显示问题的 gif 链接:https://imgur.com/a/qe2uAXj

这是一个示例项目,与我的应用程序中的情况类似:https://github.com/imattice/CellLabelExample

【问题讨论】:

    标签: ios swift uikit tableview


    【解决方案1】:

    为了清楚起见,要获得这个技巧,UILabel 通常必须在每一侧限制到它的超级视图,这样当它改变时,它的intrinsicContentSize 能够推动每一侧以适应文本。
    话虽如此,尝试用这两种方法包装 tapped 方法:

    tableView.beginUpdates()
    if !isOpen {
        detailLabel.numberOfLines = 0     
    }
    else {
        detailLabel.numberOfLines = 2     
    }
    tableView.endUpdates()
    

    当然tableview必须设置为自动大小:

    tableView.estimatedRowHeight = <#What you want#>
    tableView.rowHeight = UITableView.automaticDimension
    

    【讨论】:

    • 您好,感谢您的回复!我可以确认我已经实现了我的约束并且行高设置为自动。我还尝试了tableView:didSelectRowAttapped 方法周围的beginUpdates()endUpdates(),但我仍然看到相同的行为:第一次点击闪烁单元格并似乎更新内容,第二次点击按预期拉伸单元格。
    • 您确定您没有实现任何“高度”委托方法吗?如果您确实尝试评论他们
    • 查看我在github上找到的示例代码github.com/rayfix/MultilineDemo/blob/master/MultilineDemo/…
    • 我已更新问题以在 UI 中显示问题的 gif,以及我的 github 上的示例项目,该项目显示了我的单元格是如何设置的以及它在构建后的行为方式。
    【解决方案2】:

    我能够弄清楚发生了什么。问题分为两部分。

    第一部分是调用reloadRows()。此方法是用新单元格替换单元格,而不是更新已存在的单元格。因此,我正在更改隐藏的交换单元格上的行数,而不是视图中的单元格。 the docs 中提到了这种行为:

    重新加载一行会导致表格视图向其数据源询问该行的新单元格。该表在为旧行设置动画时为该新单元格设置动画。

    此外,我使用结构作为数据模型来跟踪单元格的打开状态。在 Swift 中,结构是写入时复制的,这意味着如果在该结构上更改了一个值,则会创建一个新结构,而不是更改我所指向的那个结构的值。这意味着 tableData[indexPath.row].isOpen = !data.isOpen 行没有做任何有用的事情 - 我们查看索引路径中的 tableData 结构,获取它的 isOpen 值,复制一个新结构并更改该新结构的 isOpen 值,然后将其抛出因为新结构没有分配到任何地方。

    解决方案是不使用reloadRows() 方法,而是使用 A) 数据对象的class B) 将 indexPath.row 处的数据替换为复制的结构体

        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            guard let cell = tableView.cellForRow(at: indexPath) as? CustomCell else { return }
            var data = tableData[indexPath.row]
    
            tableView.beginUpdates()
            cell.tapped(isOpen: data.isOpen)
            data.isOpen = !data.isOpen
    
            tableData[indexPath.row] = data
            tableView.endUpdates()
        }
    

    【讨论】: