动态改变 UITableViewCell 高度 - Swift 4

Posted

技术标签:

【中文标题】动态改变 UITableViewCell 高度 - Swift 4【英文标题】:Dynamically change UITableViewCell height - Swift 4 【发布时间】:2018-09-10 22:22:42 【问题描述】:

如何动态更改UITableViewCell 高度?我已经尝试实现以下内容,但由于某种原因,它不起作用。一旦我加载显示此表视图的视图控制器,代码就会崩溃

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat 
    let cell = tableView.cellForRow(at: indexPath) as! AvailableRideCell
    return cell.getHeight()

这是我的AvailableRideCell 中的getHeight 函数

func getHeight() -> CGFloat 
    return self.destinationLabel.optimalHeight + 8 + self.originLabel.optimalHeight + 8 + self.priceLabel.optimalHeight

这是optimalHeight函数

extension UILabel 
    var optimalHeight : CGFloat 
        get 
            let label = UILabel(frame: CGRect(x: 0, y: 0, width: self.bounds.width, height: CGFloat.greatestFiniteMagnitude))
            label.numberOfLines = 0
            label.lineBreakMode = NSLineBreakMode.byWordWrapping
            label.font = self.font
            label.text = self.text
            label.sizeToFit()
            return label.frame.height
        
    

【问题讨论】:

错误是什么? 线程 1:致命错误:在展开可选值时意外发现 nil 问题出在as!。我在下面的回答将帮助您找到正确的方向。 澄清一下,tableView.cellForRow(at: indexPath) 返回 nil。所以在给定的路径上没有单元格。将其更改为 as? 并检查 nil 并且此代码可能有效。不过,我强烈建议使用“计算器单元”方法。 我将 as! 更改为 as? 并使用 if 检查它是否不为零。它有效,但高度仍然不正确。也许我的getHeight 函数有错误? 【参考方案1】:

请记住 UITableViewCell 被重用。所以获取当前单元格的高度可能是不稳定的。

一种更好的方法是使用一个假/占位符单元格(我称之为计算器单元格)并使用它来计算单元格的大小。

所以在 heightForRowAt 方法中,您获取的是数据而不是单元格。 将该数据放入计算器单元格中,然后从那里获取高度。

【讨论】:

那么在 heightForRowAt 方法中,我如何创建一个calculatorCell?我正在尝试实现此方法,但我认为我遗漏了一些东西 @yambo,给你的视图控制器一个属性,一个缓存的单元格,你可以用它来执行计算【参考方案2】:

你的代码因为这一行而崩溃

let cell = tableView.cellForRow(at: indexPath) as! AvailableRideCell

从Apple Documentation我们知道这个方法是用来优化的。整个想法是在不浪费时间创建细胞本身的情况下获得细胞高度。所以这个方法调用before初始化任何单元格,tableView.cellForRow(at: indexPath) 返回nil。因为还没有任何细胞。但是您正在使用 as! AvailableRideCell 强制解包并且您的代码崩溃了。

所以首先,您需要了解,为什么不应该在cellForRow(at ) 方法中使用任何单元格。 之后,您需要更改逻辑,以便在不调用单元格的情况下计算内容高度。

例如,在我的项目中,我使用过这个解决方案

字符串实现

extension String 
    func height(for width: CGFloat, font: UIFont) -> CGFloat 
        let maxSize = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)
        let actualSize = self.boundingRect(with: maxSize,
                                           options: [.usesLineFragmentOrigin],
                                           attributes: [NSAttributedStringKey.font: font],
                                           context: nil)
        return actualSize.height
    

UILabel 实现

extension String 
    func height(for width: CGFloat, font: UIFont) -> CGFloat 
        let labelFrame = CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude)
        let label = UILabel(frame: labelFrame)
        label.numberOfLines = 0
        label.lineBreakMode = .byWordWrapping
        label.font = font
        label.text = self
        label.sizeToFit()
        return label.frame.height
    

这样,您需要做的就是计算您的标签并存储它的字体。

var titles = ["dog", "cat", "cow"]

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
    // number of rows is equal to the count of elements in array
    return titles.count


func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat 
    let cellTitle = titles[indexPath.row]
    return cellTitle.height(forWidth: labelWidth, font: labelFont)

动态行高变化

如果您需要更新行高,您只需在内容发生更改时调用此方法即可。 indexPath 是更改项的索引路径。

tableView.reloadRows(at: [indexPath], with: .automatic)

希望对你有帮助。

【讨论】:

谢谢!我没有意识到调用这些委托方法的顺序,这解释了很多。使用您建议的解决方案,如果尚未创建单元格并且您没有在该方法中创建单元格变量,我如何引用yourCellTitle?对不起,我的误解,我非常了解编码。 @yambo 您必须有一些容器,其中包含您将使用UITableView 显示的内容。我已经编辑了我的答案并添加了一个更详细的示例。不要担心误解。这是使用UITableView 的基础知识,您可以在网上找到很多教程。 您的解决方案非常适合UILabel 在“高度”中只有 1 行的单元格!对于高度超过 1 行的标签,我不确定如何正确使用此方法。有没有办法将字符串长度与标签宽度进行比较,看看字符串是否会在标签中超过1行? @yambo 来自字符串扩展的方法已经 计算多行字符串的高度。如果它总是返回单行的高度,则您可能传递了错误的参数。你如何计算你的标签宽度? 我正在以编程方式做所有事情,并使用锚属性使我的标签成为屏幕宽度的一半。所以由于标签填满了屏幕的一半,我通过UIScreen.main.bounds.width * 0.5作为宽度【参考方案3】:

你没有提到你是否使用自动布局,但如果你是,你可以让自动布局管理每一行的高度。你不需要实现heightForRow,而是设置:

tableView.rowHeight = UITableViewAutomaticDimension

并为您的 UILabel 配置约束,将其固定到单元格的内容视图:

    let margins = contentView.layoutMarginsGuide
    NSLayoutConstraint.activate([
        cellLabel.leadingAnchor.constraint(equalTo: margins.leadingAnchor),
        cellLabel.trailingAnchor.constraint(equalTo: margins.trailingAnchor),
        cellLabel.topAnchor.constraint(equalTo: margins.topAnchor),
        cellLabel.bottomAnchor.constraint(equalTo: margins.bottomAnchor)
    ])

每一行都会扩展或收缩以适应标签的固有内容大小。如果设备横向/纵向方向发生变化,高度会自动调整,无需重新加载单元格。如果您希望在设备的 UIContentSizeCategory 更改时自动更改行高,请设置以下内容:

cellLabel.adjustsFontForContentSizeCategory = true

【讨论】:

以上是关于动态改变 UITableViewCell 高度 - Swift 4的主要内容,如果未能解决你的问题,请参考以下文章

带有图像视图和标签的 iOS UITableViewCell 动态大小的表格视图单元格

UITableViewCell 改变高度

iOS知识学习_iOS动态改变TableView Cell高度

改变 UITableViewCell 高度的自定义动画

UITableViewCell 神秘地改变高度

UITableViewCell 的动态高度