将视图添加到 UIStackView 后 UITableViewCell 不更新高度

Posted

技术标签:

【中文标题】将视图添加到 UIStackView 后 UITableViewCell 不更新高度【英文标题】:UITableViewCell doesn't update height after adding a view to UIStackView 【发布时间】:2017-06-28 10:10:45 【问题描述】:

我在UITableViewCellcontentView 中有一个UIStackView。基于用户交互,我在UIStackView 中添加/删除项目。修改UIStackView 中的项目后,我希望cell 相应地更新它的高度。但是,除非我打电话给tableView.reloadData(),否则它不会更新它的高度。但是,在 cellForRowAtIndexPath / willDisplayCell 中调用 reloadData() 会变得递归。

在运行时根据UIStackView 中的项目调整cell 高度的正确方法是什么?

我用UITableViewAutomaticDimension

更新问题:

Here 是我正在尝试做的一个简单原型。

我的实际问题是使单元格出队。

在原型中,我有 2 个可重复使用的单元格和 3 行。对于第 0 行和第 2 行,我将 cellA 出队,对于第 1 行,我将 cellB 出队。以下是我使用的条件的概述。

if indexPath.row == 0 
    // dequeue cellA with 2 items in stackView


if indexPath.row == 1 
    // dequeue cellB with 25 items in stackView


if indexPath.row == 2 
    // dequeue cellA with 8 items in stackView

但输出是, 第 0 行包含 stackView 中的 2 个项目 - 预期 第 1 行包含 stackView 中的 25 个项目 - 预期 第 2 行包含 stackView 中的 2 项 - 意外,第 0 行已出列

我还尝试删除stackViewcellForRowAtIndexPath 中的所有排列的子视图。但是,这样做会在滚动时闪烁 UI。我怎样才能获得所需的输出?

【问题讨论】:

您是否对 StackView 的 ContentView 应用了前导、尾随、顶部、底部约束? 是的..请检查所附来源 【参考方案1】:

我认为问题在于您将视图添加到堆栈视图中。

一般来说,添加元素应该在单元格初始化时进行。

willDisplay cell: 是处理修改单元格内容属性的地方。

如果您将代码从 willDisplay cell: 移动到 cellForRowAt indexPath:,您应该会看到很大的不同。

我刚刚对您链接的代码进行了一次更改,现在这些行会根据堆栈视图内容自动调整大小。

编辑:查看了您更新的代码...问题仍然是您将排列的子视图添加到错误的位置。你可以通过调用 reloadData() 来复合它。

第二次编辑:重复使用单元格时忘记处理之前添加的子视图。

更新的代码...将您的 ViewController 代码替换为:

//
//  ViewController.swift
//

import UIKit

class ViewController: UITableViewController 

    override func viewDidLoad() 
        super.viewDidLoad()
        tableView.tableFooterView = UIView()
        tableView.estimatedRowHeight = 56
    

    override func numberOfSections(in tableView: UITableView) -> Int 
        return 1
    

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return 3
    

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

        var cell = UITableViewCell()

        if indexPath.row == 0 || indexPath.row == 2 
            cell = tableView.dequeueReusableCell(withIdentifier: "cell")!

            if let stackView = cell.viewWithTag(999) as? UIStackView 
                let numberOfItemsInStackView = (indexPath.row == 0) ? 2 : 8
                let color = (indexPath.row == 0) ? UIColor.gray : UIColor.black

                // cells are reused, so clear out any previously added subviews...
                // but leave the first view that is part of the cell prototype
                while stackView.arrangedSubviews.count > 1 
                    stackView.arrangedSubviews[1].removeFromSuperview()
                

                // use "i" so we can count
                for i in 1...numberOfItemsInStackView 

                    // use label instead of view so we can number them for testing
                    let newView = UILabel()
                    newView.text = "\(i)"
                    newView.textColor = .yellow

                    // add a border, so we can see the frames
                    newView.layer.borderWidth = 1.0
                    newView.layer.borderColor = UIColor.red.cgColor

                    newView.backgroundColor = color
                    let heightConstraint = newView.heightAnchor.constraint(equalToConstant: 54)
                    heightConstraint.priority = 999
                    heightConstraint.isActive = true
                    stackView.addArrangedSubview(newView)
                
            

        

        if indexPath.row == 1 
            cell = tableView.dequeueReusableCell(withIdentifier: "lastCell")!

            if let stackView = cell.viewWithTag(999) as? UIStackView 
                let numberOfItemsInStackView = 25

                // cells are reused, so clear out any previously added subviews...
                // but leave the first view that is part of the cell prototype
                while stackView.arrangedSubviews.count > 1 
                    stackView.arrangedSubviews[1].removeFromSuperview()
                

                // use "i" so we can count
                for i in 1...numberOfItemsInStackView 

                    // use label instead of view so we can number them for testing
                    let newView = UILabel()
                    newView.text = "\(i)"
                    newView.textColor = .yellow

                    // add a border, so we can see the frames
                    newView.layer.borderWidth = 1.0
                    newView.layer.borderColor = UIColor.red.cgColor

                    newView.backgroundColor = UIColor.darkGray
                    let heightConstraint = newView.heightAnchor.constraint(equalToConstant: 32)
                    heightConstraint.priority = 999
                    heightConstraint.isActive = true
                    stackView.addArrangedSubview(newView)
                
            

        

        return cell

    

//    override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) 
//        if cell.reuseIdentifier == "cell" 
//            if let stackView = cell.viewWithTag(999) as? UIStackView 
//                let numberOfItemsInStackView = (indexPath.row == 0) ? 2 : 8
//                let color = (indexPath.row == 0) ? UIColor.gray : UIColor.black
//                guard stackView.arrangedSubviews.count == 1 else  return 
//                for _ in 1...numberOfItemsInStackView 
//                    let newView = UIView()
//                    newView.backgroundColor = color
//                    let heightConstraint = newView.heightAnchor.constraint(equalToConstant: 54)
//                    heightConstraint.priority = 999
//                    heightConstraint.isActive = true
//                    stackView.addArrangedSubview(newView)
//                
//                tableView.reloadData()
//            
//        
//        
//        if cell.reuseIdentifier == "lastCell" 
//            if let stackView = cell.viewWithTag(999) as? UIStackView 
//                let numberOfItemsInStackView = 25
//                guard stackView.arrangedSubviews.count == 1 else  return 
//                for _ in 1...numberOfItemsInStackView 
//                    let newView = UIView()
//                    newView.backgroundColor = UIColor.darkGray
//                    let heightConstraint = newView.heightAnchor.constraint(equalToConstant: 32)
//                    heightConstraint.priority = 999
//                    heightConstraint.isActive = true
//                    stackView.addArrangedSubview(newView)
//                
//                tableView.reloadData()
//            
//        
//    

    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat 
        return UITableViewAutomaticDimension
    

【讨论】:

我已经更新了我面临的问题。我面临的实际问题是使单元出队。我还更新了定义问题的来源 感谢您更新代码。我在原型中使用了您的代码。它有一个出队问题。第三行预计有 8 个项目,但它只包含 2 个 是的 - 我稍微改变了大小,所以我可以同时看到所有三行......这导致我没有注意到当单元格被重用时我忘记处理删除以前添加的视图.请参阅我的“第二次编辑”代码。【参考方案2】:

尝试仅使用以下方式重新加载单元格:https://developer.apple.com/documentation/uikit/uitableview/1614935-reloadrows

示例代码

这是一个例子。我们在视图控制器中有基本的表格视图单元(TableViewCell)。单元格在堆栈视图中有 2 个标签。我们可以使用折叠/显示方法隐藏或显示第二个标签。

class TableViewCell : UITableViewCell 
    @IBOutlet private var stackView: UIStackView!
    @IBOutlet private var firstLabel: UILabel!
    @IBOutlet private var secondLabel: UILabel!

    func collapse() 
        secondLabel.isHidden = true
    

    func reveal() 
        secondLabel.isHidden = false
    


class ViewController : UIViewController 

    @IBOutlet var tableView: UITableView!

    fileprivate var collapsedCells: Set<IndexPath> = []

    override func viewDidLoad() 
        super.viewDidLoad()
        tableView.rowHeight = UITableViewAutomaticDimension
        tableView.estimatedRowHeight = 128
    

    @IBAction private func buttonAction(_ sender: Any) 
        collapseCell(at: IndexPath(row: 0, section: 0))
    

    private func collapseCell(at indexPath: IndexPath) 
        if collapsedCells.contains(indexPath) 
            collapsedCells.remove(indexPath)
         else 
            collapsedCells.insert(indexPath)
        
        tableView.reloadRows(at: [indexPath], with: .automatic)
    



extension ViewController : UITableViewDataSource 
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TableViewCell
        if collapsedCells.contains(indexPath) 
            cell.collapse()
         else 
            cell.reveal()
        
        return cell
    

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return 10
    

【讨论】:

我已附上问题的代码。我已经在我的代码中注释了 reloadRowsAtIndexPaths 行。只需取消注释并检查应用的行为 @ios 我加了一个完整的例子 我已经更新了我面临的问题。我面临的实际问题是使单元出队。我还更新了定义问题的来源

以上是关于将视图添加到 UIStackView 后 UITableViewCell 不更新高度的主要内容,如果未能解决你的问题,请参考以下文章

如何将 UIImageViews 添加到受限于视图的 centerXAnchor 的 UIStackView?

如何以编程方式将笔尖添加到 UIStackView

快速将“n”个集合视图添加到 UIStackView

将 uipageviewcontroller 添加到 uistackview

如何将一组 UIButtons 添加到 UIStackview 上?

以编程方式将一堆自定义视图从 XIB 添加到 UIStackView