UITableView 单元格折叠动画看起来很糟糕

Posted

技术标签:

【中文标题】UITableView 单元格折叠动画看起来很糟糕【英文标题】:UITableView cell collapse animation looks bad 【发布时间】:2018-05-23 10:31:54 【问题描述】:

我在使单元格折叠动画看起来始终流畅时遇到问题。

我的形象值一千字。这是同一个表格视图的两个 GIF:

折叠对单元格“6”非常有效,但不适用于“5”。看起来整个 UITableView 内容在执行折叠动画之前都跳了起来。

UITableViewAutomaticDimension 用于UITableView 的rowHeight。相反,我通过tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) 方法提供行高,并使用表格视图的beginUpdates/endUpdates 展开/折叠单元格。如您所见,我还使用tableView.scrollToRow(at: indexPath, at: .none, animated: true) 滚动到展开的行(但仅在展开时,因此可能无关紧要)。

UITableViewController 的子类嵌入在带有大标题和搜索栏的 UINavigationController 中(此处不可见,因为内容向下滚动)。 UINavigationController 嵌入在 UITabBarController 中。

这是相关的展开/折叠部分。当传递一个 nil indexPath 参数时,已经展开的单元格将折叠。

func expandRow(at indexPath: IndexPath?) 
    selectedIndexPath = indexPath
    tableView.beginUpdates()
    tableView.endUpdates()

    if let ip = indexPath 
        tableView.scrollToRow(at: ip, at: .none, animated: true)
    

这个方法计算行高,tableView(_:heightForRowAt:)tableView(_:estimatedHeightForRowAt:)都调用:

private func heightForRow(at indexPath: IndexPath) -> CGFloat 
    var height: CGFloat
    if selectedIndexPath == indexPath 
        // Add some height variety
        height = 300 + CGFloat((indexPath.row % 4) * 20)
     else 
        height = 70
    
    return height

这里是 GitHub 代码库:https://github.com/AleksanderMaj/CellCollapse

知道如何改进吗?

此问题与尺寸有关(在具有不同屏幕尺寸的设备上执行相同步骤后,此问题可能无法重现)。这是在装有 ios 11.3 的 iPhone 7 模拟器上录制的。


编辑 1: 保持自动滚动到展开的单元格很重要。

【问题讨论】:

你认为.beginUpdates() .endUpdates()这两条线在做什么? 据我了解,这是一种众所周知的技术,它使表格视图以动画方式更新单元格高度。单元格高度的计算方式如下:(我粘贴在原始问题中,因为cmets中没有代码格式) @AleksanderMaj 你找到解决办法了吗? @guru 不幸的是没有。我很久以前就放弃了 【参考方案1】:

通过对您的扩展方法进行微小更改,我能够使操作变得顺利:

func expandRow(at indexPath: IndexPath?) 
    selectedIndexPath = indexPath
    tableView.beginUpdates()
    tableView.endUpdates()

    if let ip = indexPath 
        tableView.scrollToRow(at: ip, at: .middle, animated: true)
    

【讨论】:

这个解决方案会导致大多数顶部和底部单元格的意外跳转monosnap.com/file/TBg6Wl9iZRoH9SUqVmaLSFYUO6V9zc 感谢您的回答,但正如@TarasChernyshenko 指出的那样,它引入了其他问题。【参考方案2】:

在为每行点击调整行高时遇到此问题。与@Taras 的答案几乎相同,虽然在这里,需要先删除动画,然后再分配保存的 contentOffset。希望这会有所帮助:)

func expandRow() 
    let savedContentOffset = tableView.contentOffset

    tableView.beginUpdates()
    tableView.endUpdates()
    tableView.layer.removeAllAnimations()

    tableView.setContentOffset(savedContentOffset, animated: false)

【讨论】:

【参考方案3】:

您正在滚动到展开的行,这使得 UITableView 有点跳跃。

所以这里有一个重构的expandRow(at:) 方法:

func expandRow(at indexPath: IndexPath?) 
    self.contentOffset = self.tableView.contentOffset
    selectedIndexPath = indexPath
    tableView.beginUpdates()
    tableView.endUpdates()

    if let contentOffset = self.contentOffset 
        tableView.contentOffset = contentOffset
    

您还需要将var contentOffset: CGPoint? 属性添加到您的控制器。

更新 我错了,你不需要把selectedIndexPath = indexPath btw beginUpdates()endUpdates() 方法。更新代码示例

【讨论】:

感谢您的回复。滚动部分仅在展开时(即indexPath != nil时)执行,对折叠单元格没有影响。这不是问题的一部分。您的响应的 contentOffset 部分根本不起作用。展开单元格“5”,然后手动滚动,使其底部边缘与选项卡栏的顶部对齐。然后再次点击“5”以折叠它。它还在跳:youtu.be/kvyjKrWnlnM【参考方案4】:

我有个主意。我不确定这个技巧是否像我想象的那样奏效。

Collapsible table 有一个特性,就是在展开或折叠时,table view 的高度会发生变化。

如果我们使用继承自 UITableView 的自定义视图,我们可以覆盖视图的属性 contentSize 和 contentOffset。 通过覆盖这两个属性,我们可以让表格视图停止,禁用滚动,一段时间。

var contentSizeChanging: Bool = false

override var contentSize: CGSize 
    willSet 
        contentSizeChanging = true
    
    didSet 
        contentSizeChanging = false
    


override var contentOffset: CGPoint 
    get 
        return super.contentOffset
    
    set(newValue) 
        if contentSizeChanging  return 
        super.contentOffset = newValue
    

【讨论】:

以上是关于UITableView 单元格折叠动画看起来很糟糕的主要内容,如果未能解决你的问题,请参考以下文章

当上边距不完全可见时,UITableView 单元格展开动画会发生变化

UITableView - 当它们离开屏幕时不要卸载单元格

UITableview 单元格高度在空表中不起作用?

动画在具有动态单元格高度的单元格中添加子视图

UITextView 的建议位于 UITableView 的浮动标题下

RowAnimation 使用不同高度的单元格看起来很奇怪