Swift - tableView 行高仅在滚动或切换后更新 expand/collapse

Swift - tableView Row height updates only after scrolling or toggle expand/collapse

我正在使用 CollapsibleTableView from here and modified it as per my requirement to achieve collapsible sections. Here is how it looks now

由于根据 UI 设计,我的部分有边框,因此我选择 header 部分作为我的 UI 元素,它在折叠和展开时都保存数据模式。

原因:我试过了,但无法在下面解释的这个模型中工作 -

** 在 header 节中包含我的 header 元素,并在其单元格中包含每个项目的详细信息。默认情况下,该部分处于折叠状态。当用户点击 header 时,单元格会切换显示。正如我所说,由于需要向整个部分(点击 header 及其单元格)显示一个边框,我选择 header 部分作为我的 UI 操作元素。这是我的 tableView -

代码
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return sections.count 
    }

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        switch indexPath.row {
        case 0:
            return sections[indexPath.section].collapsed! ? 0 : (100.0 + heightOfLabel2!)
        case 1:
            return 0
        case 2:
            return 0
        default:
            return 0
        }
    }


func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {

        let header = self.tableView.dequeueReusableHeaderFooterViewWithIdentifier("header") as! CollapsibleTableViewHeader

        if sections.count == 0 {
            self.tableView.userInteractionEnabled = false
            header.cornerRadiusView.layer.borderWidth = 0.0
            header.benefitAlertImage.hidden = true
            header.benefitAlertText.hidden = true
            header.amountLabel.hidden = true
            header.titleLabel.text = "No_Vouchers".localized()
        }
        else {
            header.amountLabel.hidden = false
            header.cornerRadiusView.layer.borderWidth = 1.0
            self.tableView.userInteractionEnabled = true
            header.titleLabel.text = sections[section].name
            header.arrowImage.image = UIImage(named: "voucherDownArrow")
            header.setCollapsed(sections[section].collapsed)

            let stringRepresentation = sections[section].items.joinWithSeparator(", ")

            header.benefitDetailText1.text = stringRepresentation
            header.benefitDetailText2.text = sections[section].shortDesc
            header.benefitDetailText3.text = sections[section].untilDate

            header.section = section
            header.delegate = self

            if sections[section].collapsed == true {
                header.benefitAlertImage.hidden = true
                header.benefitAlertText.hidden = true
            }
            else {
                if sections[section].isNearExpiration == true {
                    header.benefitAlertImage.hidden = false
                    header.benefitAlertText.hidden = false
                }
                else {
                    header.benefitAlertImage.hidden = true
                    header.benefitAlertText.hidden = true
                }
            }

            if appLanguageDefault == "nl" {
                self.totalAmountLabel.text = "€ \(sections[section].totalAvailableBudget)"
            }
            else {
                self.totalAmountLabel.text = "\(sections[section].totalAvailableBudget) €"
            }
        }

        return header
    }

切换函数collapse/expand - 我在该部分内使用 "dynamically changing" UI 标签的高度值,然后使用这些值来扩展边框(使用其布局约束)。

func toggleSection(header: CollapsibleTableViewHeader, section: Int) {
        let collapsed = !sections[section].collapsed

        header.benefitAlertImage.hidden = true
        header.benefitAlertText.hidden = true
        // Toggle collapse
        sections[section].collapsed = collapsed
        header.setCollapsed(collapsed)

        // Toggle Alert Labels show and hide
        if sections[section].collapsed == true {
            header.cornerRadiusViewBtmConstraint.constant = 0.0
            header.cornerRadiusViewTopConstraint.constant = 20.0
            header.benefitAlertImage.hidden = true
            header.benefitAlertText.hidden = true
        }
        else {

            heightOfLabel2 = header.benefitDetailText2.bounds.size.height

            if sections[section].isNearExpiration == true {
                header.benefitAlertImage.hidden = false
                header.benefitAlertText.hidden = false
                header.cornerRadiusViewBtmConstraint.constant = -100.0 - heightOfLabel2!
                header.cornerRadiusViewTopConstraint.constant = 10.0
                if let noOfDays = sections[section].daysUntilExpiration {
                    if appLanguageDefault == "nl" {

                        header.benefitAlertText.text = "(nog \(noOfDays) dagen geldig)"
                    }
                    else {
                        header.benefitAlertText.text = "(valable encore \(noOfDays) jour(s))"
                    }
                }                
            }
            else {
                header.cornerRadiusViewBtmConstraint.constant = -80.0 - heightOfLabel2!
                header.cornerRadiusViewTopConstraint.constant = 20.0
                header.benefitAlertImage.hidden = true
                header.benefitAlertText.hidden = true
            }
        }

        // Adjust the height of the rows inside the section
        tableView.beginUpdates()
        for i in 0 ..< sections.count {
            tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: i, inSection: section)], withRowAnimation: .Automatic)
        }
        tableView.endUpdates()
    }

问题: 根据某些条件,我需要在首次启动视图时默认展开此 table 视图中的几个 headers 部分。当我计算标签的高度并使用高度设置边框的顶部和底部约束时,很难按照设计显示扩展部分 header。

由于我的UI标签的高度默认为 21,因此内容超出了边框。

UPDATE:行高仅在滚动视图后或在 collapse/expand

之间切换时发生变化

问题: 如何在第一次启动视图时计算 UI 部分 header 中显示的标签的高度? (这意味着,在我的 REST 调用完成后,将获取数据,然后我需要获取 UI 标签高度)。

目前,我正在使用heightOfLabel2 = header.benefitDetailText2.bounds.size.height

(或)

有没有更好的方法来实现这个?

提前致谢!

您可以试试这个字符串扩展来计算边界矩形

extension String {
    func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
        let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
        let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)

        return boundingBox.height
    }
}

来源:

如果我对你的问题的理解正确,你要做的是在调用 toggleSection.

时调整 tableHeaderView 的大小

因此,您 tableHeaderView 需要做的是调整大小

// get the headerView
let headerView = self.tableView(self.tableView, viewForHeaderInSection: someSection)

// tell the view that it needs to refresh its layout
headerView?.setNeedsDisplay()

// reload the tableView
tableView.reloadData() 
/* or */ 
// beginUpdates, endUpdates

基本上你要做的是将上面的代码片段放在你的函数中 toggleSection(header: CollapsibleTableViewHeader, section: Int)

func toggleSection(header: CollapsibleTableViewHeader, section: Int) {
    ...

    // I'm not sure if this `header` variable is the headerView so I'll just add my code snippet at the bottom
    header.setNeedsDisplay() 


    /* code snippet start */
    // get the headerView
    let headerView = self.tableView(self.tableView, viewForHeaderInSection: someSection)

    // tell the view that it needs to refresh its layout
    headerView?.setNeedsDisplay()
    /* code snippet end */

    // reload the tableView
    // Adjust the height of the rows inside the section
    tableView.beginUpdates()


    // You do not need this
    for i in 0 ..< sections.count {
        tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: i, inSection: section)], withRowAnimation: .Automatic) 
    }
    // You do not need this


    tableView.endUpdates()
}

解释:tableView 的 headerView/footerView 不会更新其布局,即使您调用 reloadData()beginUpdates,endUpdates。您需要先告诉视图它需要更新,然后再刷新 tableView

最后你还需要应用这两个代码

func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat {
    return estimatedHeight
}

func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return UITableViewAutomaticDimension
}

以下是我根据对 OP 总体目标的理解所做的工作。如果我误解了,下面仍然是一个工作示例。完整的工作项目也在下面链接。

目标:

  • 动态大小的 TableViewCells 也是
  • 可折叠到 show/hide 其他详细信息

我尝试了多种不同的方法,这是我唯一可以开始工作的方法。

概览

设计使用了以下内容:

  • 自定义 TableViewCells
  • 自动布局
  • TableView 自动维度

因此,如果您不熟悉这些(尤其是自动版式),可能需要先复习一下。

动态 TableViewCells

界面生成器

布置您的原型单元格。增加行高大小是最简单的。简单地从几个元素开始,以确保您可以让它正常工作。 (尽管添加到 Autolayout 中可能会很痛苦)。例如,简单地垂直堆叠两个标签,布局的整个宽度。为 "title" 制作顶部标签 1 行,为 "details"

制作第二个 0 行

重要提示:要将标签和文本区域配置为增长到其内容的大小,您必须将标签设置为 0 行并将文本区域设置为不可滚动。这些是适合内容的触发器。

最重要的是确保每个元素的所有四个边都有约束。这对于使自动尺寸标注工作至关重要。

CollapsableCell

接下来我们为那个 table 单元格原型做一个非常基本的自定义 class。将标签连接到自定义单元 class 中的出口。例如:

class CollapsableCell: UITableViewCell {

  @IBOutlet weak var titleLabel: UILabel!
  @IBOutlet weak var detailLabel: UILabel!

}

从两个标签开始是最简单的。

还要确保在 Interface Builder 中将原型单元 class 设置为 CollapsableCell 并为其提供重用 ID。

CollapsableCellViewController

进入 ViewController。首先是自定义 TableViewCells 的标准内容:

  override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return data.count
  }


  override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCell(withIdentifier: "collapsableCell", for: indexPath) as! CollapsableCell
    let item = data[indexPath.row]

    cell.titleLabel?.text = item.title
    cell.detailLabel?.text = item.detail

    return cell
  }

我们使用我们的自定义单元格为给定行添加了函数 return 行数和 return 单元格。希望一切都简单明了。

现在通常会有一个函数,TableView(heightForRowAt:),这是必需的,但不要添加它(如果有的话,也可以将其删除)。这就是 Auto Dimension 发挥作用的地方。将以下内容添加到 viewDidLoad:

  override func viewDidLoad() {
    ...
    // settings for dynamic resizing table cells
    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 50
    ...
  }

此时,如果您如上所述将详细信息标签设置为 0 行,并且 运行 项目,您应该会根据要放入其中的文本量获得不同大小的单元格标签。动态 TableViewCells 完成了。

可折叠单元格

要添加 collapse/expand 功能,我们可以构建我们目前正在使用的动态大小调整。将以下函数添加到 ViewController:

  override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    guard let cell = tableView.cellForRow(at: indexPath) as? CollapsableCell else { return }
    let item = data[indexPath.row]

    // update fields
    cell.detailLabel.text = self.isExpanded[indexPath.row] ? item.detail1 : ""

    // update table
    tableView.beginUpdates()
    tableView.endUpdates()

    // toggle hidden status
    isExpanded[indexPath.row] = !isExpanded[indexPath.row]

  }

同时将 'var isExpanded = Bool' 添加到您的 ViewController 以存储行的当前展开状态(这也可以是您自定义 TableViewCell 中的 class 变量)。

构建并单击其中一行,它应该会缩小以仅显示标题标签。这就是基础。

示例项目: github 提供了一个包含更多字段和披露人字形图像的工作示例项目。这还包括一个单独的视图,其中包含一个基于内容动态调整大小的 Stackview 演示。

一些注意事项:

  • 这都是在普通的 TableViewCells 中完成的。我知道 OP 正在使用 header 个单元格,虽然我想不出为什么它不能以同样的方式工作的原因,但没有必要那样做。
  • 添加和删除子视图是我最初认为最有效且最有效的方法,因为视图可以从 nib 加载,甚至可以存储以备 re-added。出于某种原因,在添加子视图后我无法调整它的大小。我想不出它不起作用的原因,但这里有一个解决方案。

在这个方法中,

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        switch indexPath.row {
        case 0:
            return sections[indexPath.section].collapsed! ? 0 : (100.0 + heightOfLabel2!)
        case 1:
            return 0
        case 2:
            return 0
        default:
            return 0
        }
    }

而不是使用heightOfLabel2,尝试实现以下方法来计算每个单元格特定的高度(因为我们知道要填充的文本,它的字体和标签宽度,我们可以计算标签的高度) ,

func getHeightForBenefitDetailText2ForIndexPath(indexPath: NSIndexPath)->CGFloat

所以你的方法应该是这样的,

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        switch indexPath.row {
        case 0:
            return sections[indexPath.section].collapsed! ? 0 : (100.0 + getHeightForBenefitDetailText2ForIndexPath(indexPath))
        case 1:
            return 0
        case 2:
            return 0
        default:
            return 0
        }
    }

关于您第一次将几个单元格扩展的问题,请确保在重新加载 table 之前将这些单元格的 collapsed 属性 设置为 true。 =15=]

作为性能改进,您可以将为每个展开的单元格计算的高度值存储在字典中,并 return 存储字典中的值,以避免一次又一次地进行相同的计算。

希望对您有所帮助。如果没有,请分享一个示例项目以更深入地了解您的问题。