Ios UITableView,具有动态高度单元的手风琴式动画

Ios UITableView,具有动态高度单元的手风琴式动画,ios,swift,uitableview,uistackview,Ios,Swift,Uitableview,Uistackview,我正试图在一个带有动态高度单元的UITableView中创建一个类似手风琴的动画 首次尝试使用重载行 问题似乎是重新加载行会导致单元格高度的即时更新,因此尽管tableview会设置动画,但单元格本身不会设置其高度的动画。这导致其他细胞有时重叠并干扰扩展的细胞内容 第二次尝试使用BeginUpdate/EndUpdate 这是我找到的最接近细胞按需要膨胀的地方。然而,当折叠时,TeTeVIEW立即消失,使标题挂在中间,直到崩溃。 目标 我尝试实现的效果与尝试2中展开时的效果完全相同,但在折叠

我正试图在一个带有动态高度单元的UITableView中创建一个类似手风琴的动画

首次尝试使用重载行

问题似乎是重新加载行会导致单元格高度的即时更新,因此尽管tableview会设置动画,但单元格本身不会设置其高度的动画。这导致其他细胞有时重叠并干扰扩展的细胞内容

第二次尝试使用BeginUpdate/EndUpdate

这是我找到的最接近细胞按需要膨胀的地方。然而,当折叠时,TeTeVIEW立即消失,使标题挂在中间,直到崩溃。 目标 我尝试实现的效果与尝试2中展开时的效果完全相同,但在折叠时效果也相反(手风琴/展示风格)

注意:如果可能的话,我还希望继续使用StackView在UITextView上设置
hidden
属性,因为我正在处理的实际项目涉及其他几个子视图,这些子视图的动画应该与UITextView类似

我使用的是XCode 11 beta 7

首次尝试使用重载行的代码: 表格控制器 细胞 故事板

使用BeginUpdate/EndUpdate尝试2的代码 与尝试1完全相同的代码,不同的是,
…didSelectRowAt…
替换为:

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

    if let selectedRow = selectedRow,
        let prevCell = tableView.cellForRow(at: selectedRow) as? MyTableViewCell {
        prevCell.configureExpansion(false)
    }

    let selectedCell = tableView.cellForRow(at: indexPath) as? MyTableViewCell
    if selectedRow == indexPath {
        selectedCell?.configureExpansion(false)
        selectedRow = nil
    } else {
        selectedCell?.configureExpansion(true)
        selectedRow = indexPath
    }

    tableView.endUpdates()
}

我最终在BeginUpdate/EndUpdate的基础上解决了这个问题。接下来我将进一步研究这个问题和解决方案

问题 如原问题所述,扩展阶段工作正常。这样做的原因是:

  • 将UITextView的
    isHidden
    属性设置为
    false
    时,包含的堆栈视图会调整大小,并为单元格提供新的固有内容大小
  • 使用BeginUpdate/EndUpdate将在不重新加载单元格的情况下设置行高更改的动画。开始状态是UITextView的内容可见,但当前被当前单元格高度剪裁,因为单元格尚未调整大小
  • 调用
    endUpdates
    时,单元格将自动展开,因为内部大小已更改且
    tableView.rowHeight=.automaticDimension
    。动画将根据需要逐渐显示新内容
  • 但是,折叠阶段不会产生相反的动画效果。原因是:

  • 将UITextView的
    isHidden
    属性设置为
    true
    时,包含堆栈的视图会隐藏视图,并将单元格调整为新的固有内容大小
  • 调用
    endUpdates
    时,动画将通过立即删除UITextView开始。想一想,这是意料之中的,因为这与扩张期间发生的情况相反。然而,它也打破了预期的动画效果,留下可见元素“挂”在中间,而不是逐渐隐藏UITExtVIEW当行收缩。< /LI> 解决办法 要获得所需的隐藏效果,当单元格收缩时,UITextView应在整个动画期间保持可见。这包括几个步骤:

    步骤1:覆盖
    heightForRow

    override func tableView(_ tableView: UITableView,
                            heightForRowAt indexPath: IndexPath) -> CGFloat {
        // If a cell is collapsing, force it to its original height stored in collapsingRow?
        // instead of using the intrinsic size and .automaticDimension
        if indexPath == collapsingRow?.indexPath {
            return collapsingRow!.height
        } else {
            return UITableView.automaticDimension
        }
    }
    
    当单元收缩时,UITextView现在将保持可见,但由于自动布局,UITextView会立即被剪裁,因为它定位到单元高度

    步骤2:在UIStackView上创建高度约束,并使其在单元格收缩时具有优先权

    2.1:在Interface builder中的UIStackView上添加了高度约束

    2.2:在MyTableViewCell中添加了
    heightConstraint
    作为strong(不弱,这很重要)出口

    2.3在MyTableViewCell的
    awakeFromNib
    中,设置
    heightConstraint.isActive=false
    以保持默认行为

    2.4在interface builder中:确保UIStackView底部约束的优先级设置低于高度约束的优先级。即底部约束的
    999
    ,高度约束的
    1000
    。如果不这样做,则会在折叠阶段导致约束冲突

    步骤3:折叠时,激活
    高度约束
    并将其设置为UIStackView的当前固有大小。这样可以在单元格高度降低时保持UITextView的内容可见,但也可以根据需要剪裁内容,从而产生“隐藏”效果

    步骤4:使用
    CATTransaction.setCompletionBlock完成动画时重置状态

    这些步骤结合在一起

    你得到的正是我所期望的结果。您正在更改单元格内容,然后进行手风琴演奏。这正是我们所看到的。好吧,这不是我所期望的。我期待一个类似的动画:啊,但这是一个零高度折叠没有内容变化。所以你没有理由期待。这很容易实现,但不是你正在做的。好吧,jqueryui.com/accordion/#collapsable的问题是标题和内容是不同的对象-你可以使用不同的标签来解决这个问题。展开时,1)创建另一个标签,或2)已经有一个要翻转的标签visible@matt这不是一个很有帮助的评论。我想,如果你知道解决方案,这很容易实现,但如果我有解决方案,我不会问
    class MyTableViewCell: UITableViewCell {
        @IBOutlet weak var title: UILabel!
        @IBOutlet weak var textView: UITextView!
    
        func configure(expanded: Bool, post: Post) {
            title.text = post.title
            textView.text = post.content
            configureExpansion(expanded)
        }
    
        func configureExpansion(_ expanded: Bool) {
            self.textView.isHidden = !expanded
            self.contentView.backgroundColor = expanded ?
                .red : .systemBackground
        }
    }
    
    override func tableView(_ tableView: UITableView,
                            didSelectRowAt indexPath: IndexPath) {
        tableView.beginUpdates()
    
        if let selectedRow = selectedRow,
            let prevCell = tableView.cellForRow(at: selectedRow) as? MyTableViewCell {
            prevCell.configureExpansion(false)
        }
    
        let selectedCell = tableView.cellForRow(at: indexPath) as? MyTableViewCell
        if selectedRow == indexPath {
            selectedCell?.configureExpansion(false)
            selectedRow = nil
        } else {
            selectedCell?.configureExpansion(true)
            selectedRow = indexPath
        }
    
        tableView.endUpdates()
    }
    
    override func tableView(_ tableView: UITableView,
                            heightForRowAt indexPath: IndexPath) -> CGFloat {
        // If a cell is collapsing, force it to its original height stored in collapsingRow?
        // instead of using the intrinsic size and .automaticDimension
        if indexPath == collapsingRow?.indexPath {
            return collapsingRow!.height
        } else {
            return UITableView.automaticDimension
        }
    }
    
    if let expandedRow = expandedRow,
        let prevCell = tableView.cellForRow(at: expandedRow.indexPath) as? MyTableViewCell {
        prevCell.heightConstraint.constant = prevCell.stackView.frame.height
        prevCell.heightConstraint.isActive = true
    
        collapsingRow = expandedRow
    }
    
    class MyTableViewController: UITableViewController {
        var expandedRow: (indexPath: IndexPath, height: CGFloat)? = nil
        var collapsingRow: (indexPath: IndexPath, height: CGFloat)? = nil
    
        override func viewDidLoad() {
            super.viewDidLoad()
            self.tableView.backgroundColor = .darkGray
            self.tableView.rowHeight = UITableView.automaticDimension
            self.tableView.estimatedRowHeight = 200
        }
    
        override func numberOfSections(in tableView: UITableView) -> Int {
            return 1
        }
    
        override func tableView(_ tableView: UITableView,
                                numberOfRowsInSection section: Int) -> Int {
            return posts.count
        }
    
        override func tableView(_ tableView: UITableView,
                                cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            guard let cell = tableView.dequeueReusableCell(
                withIdentifier: "Cell", for: indexPath) as? MyTableViewCell
                else { fatalError() }
    
            let post = posts[indexPath.row]
            let isExpanded = expandedRow?.indexPath == indexPath
            cell.configure(expanded: isExpanded, post: post)
    
            return cell
        }
    
        override func tableView(_ tableView: UITableView,
                                heightForRowAt indexPath: IndexPath) -> CGFloat {
            if indexPath == collapsingRow?.indexPath {
                return collapsingRow!.height
            } else {
                return UITableView.automaticDimension
            }
        }
    
        override func tableView(_ tableView: UITableView,
                                didSelectRowAt indexPath: IndexPath) {
            guard let tappedCell = tableView.cellForRow(at: indexPath) as? MyTableViewCell
                else { return }
    
            CATransaction.begin()
            tableView.beginUpdates()
    
            if let expandedRow = expandedRow,
                let prevCell = tableView.cellForRow(at: expandedRow.indexPath)
                    as? MyTableViewCell {
                prevCell.heightConstraint.constant = prevCell.stackView.frame.height
                prevCell.heightConstraint.isActive = true
    
                CATransaction.setCompletionBlock {
                    if let cell = tableView.cellForRow(at: expandedRow.indexPath)
                        as? MyTableViewCell {
                        cell.configureExpansion(false)
                        cell.heightConstraint.isActive = false
                    }
                    self.collapsingRow = nil
                }
    
                collapsingRow = expandedRow
            }
    
    
            if expandedRow?.indexPath == indexPath {
                collapsingRow = expandedRow
                expandedRow = nil
            } else {
                tappedCell.configureExpansion(true)
                expandedRow = (indexPath: indexPath, height: tappedCell.frame.height)
            }
    
            tableView.endUpdates()
            CATransaction.commit()
        }
    }