Swift - tableView 行高仅在滚动或切换展开/折叠后更新

Posted

技术标签:

【中文标题】Swift - tableView 行高仅在滚动或切换展开/折叠后更新【英文标题】:Swift - tableView Row height updates only after scrolling or toggle expand/collapse 【发布时间】:2017-11-26 20:47:38 【问题描述】:

我正在使用CollapsibleTableView from here 并根据我的要求对其进行了修改以实现可折叠部分。 Here is how it looks now。

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

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

** 在部分标题中包含我的标题元素,在其单元格中包含每个项目的详细信息。默认情况下,该部分处于折叠状态。当用户点击标题时,单元格将切换为显示。正如我所说,由于需要向整个部分(点击的标题及其单元格)显示边框,因此我选择了部分标题作为我的 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
    

切换折叠/展开功能 - 我在部分内使用“动态变化”的 UILabel 的高度值,然后使用这些值来扩展边框(使用它的 layoutconstraint)。

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()
    

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

由于我的 UILabel 的高度默认为 21,因此内容超出了边界。

更新:只有在我滚动视图或在折叠/展开之间切换时,行高才会改变

问题: 如何在第一次启动视图时计算部分标题中存在的 UILabel 的高度? (也就是说,在我的 REST 调用完成后,会获取数据,然后我需要获取 UIlabel 高度)。

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

(或)

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

提前致谢!

【问题讨论】:

reloadData 会帮助你, 【参考方案1】:

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

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
    

来源:Figure out size of UILabel based on String in Swift

【讨论】:

第一次展示我的视图时,这会起作用吗?就像我的问题一样?事实上,我之前尝试过,但最大有限幅度存在问题,不再支持 当你已经有数据时,你要求计算标签的高度。所以你可以通过字符串调用这个方法,它会给你高度。 但 greatFiniteMagnitude 说“CGFloat 没有成员 bestFiniteMagnitude” 您使用的是哪个 swift 版本? 我使用的是 Swift 2.3【参考方案2】:

如果我正确理解了您的问题,您要做的是在您致电 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

基本上你要做的就是把上面的代码sn-p放在你的函数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()

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

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

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


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

【讨论】:

感谢 Zonily 的回答,有时间我会试试的。 另外,我不只是在寻找切换后会发生什么,而是我实际上需要每个部分中 tableView 标题的高度,以便展开的行看起来没有任何 UI 问题 如果你在tableHeaderView 上正确使用了 NSLayoutConstraints,它会扩展。顺便说一句,当您的意思是扩展行时,您的意思是您希望 tableViewCells 也扩展。如果你愿意,我可能不得不稍微修改一下我的答案以适应这一点 是的,我认为单元格也应该展开,否则展开的标题会与下一部分重叠。我正要问 NSLayoutConstraints - 添加约束时应该注意什么?我有 8 个标签,两个图像,一个 UIView(有 9 个图像)里面 啊不,如果tableHeaderView 扩展了,因为你现在使用的是estimatedHeightForHeaderInSection,它里面的细节不会再和你的tableViewCells 重叠了。对于您的UIControls,您应该相互约束。【参考方案3】:

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

目标:

动态大小的 TableViewCells 也是 可折叠以显示/隐藏其他详细信息

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

概述

设计使用以下内容:

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

因此,如果您不熟悉这些内容(尤其是 Autolayout,可能需要先查看一下。

动态 TableViewCells

界面生成器

布置你的原型细胞。增加行高大小是最简单的。只需从几个元素开始,以确保您可以使其正常工作。 (即使添加到 Autolayout 中可能会很痛苦)。例如,只需垂直堆叠两个标签,全宽布局。将顶部标签 1 行作为“标题”,将第二个 0 行作为“详细信息”

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

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

可折叠单元

接下来我们为该表格单元原型制作一个非常基本的自定义类。将标签连接到自定义单元类中的出口。例如:

class CollapsableCell: UITableViewCell 

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


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

还要确保在 Interface Builder 中将原型单元类设置为 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
  

我们添加了函数来返回行数并使用我们的自定义单元格返回给定行的单元格。希望一切都直截了当。

现在通常需要另外一个函数,TableView(heightForRowAt:),但不要添加它(如果有的话,也不要添加它)。这就是 Auto Dimension 的用武之地。将以下内容添加到 viewDidLoad:

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

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

可折叠单元格

要添加折叠/展开功能,我们可以构建我们目前正在使用的动态调整大小。在 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 中的类变量)。

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

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

一些注意事项:

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

【讨论】:

要将显示/隐藏功能限制为在展开单元格的顶部点击,this post 上的示例应该可以工作,但没有尝试过。检查 y 位置是否在非扩展部分中将是直截了当的。【参考方案4】:

在这个方法中,

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
        
    

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

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

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

【讨论】:

以上是关于Swift - tableView 行高仅在滚动或切换展开/折叠后更新的主要内容,如果未能解决你的问题,请参考以下文章

奇怪的行为设置 tableView 行高和 scrollPosition Swift

swift - 将行高设置为0时,tableview最后一部分被打乱

Swift设置自动行高

Swift:Xcode tableView 无限滚动

swift - 快速滚动时如何阻止tableview单元格出现故障和闪烁的图像?

更改表格宽度时,Swift Tableview 向上移动