UICollectionView 单元格展开动画问题

Posted

技术标签:

【中文标题】UICollectionView 单元格展开动画问题【英文标题】:UICollectionView Cell Expansion Animation Problem 【发布时间】:2020-05-19 20:12:49 【问题描述】:

我遇到了一个问题,我的 UICollectionView 使用 UICollectionViewCompositionalLayout + Diffable 数据源不允许我平滑地为垂直单元格展开动画。我有一列类似于 UITableView 的全角单元格,我正在尝试扩展单元格以在单元格选择时显示截断的文本。

这在没有动画的情况下可以正常工作,但是当我尝试对其进行动画处理时,如果扩展单元格下方的单元格最终位置超出屏幕边界,我无法让它看起来流畅,因为单元格会立即出现消失而不是被动画向下“推”。这个问题可以在链接的gif中看到https://imgur.com/a/ulj0p6n

如果扩展单元格下方的单元格的最终位置在屏幕边界内,则不会出现此问题,如下所示:https://imgur.com/a/uJ2RRRn。

我发现了一个似乎有类似问题的旧问题:UICollectionView animating cell size change causes undesired behavior

那里的答案很老,对我没有帮助,因为我没有使用 Flow Layout,也没有使用 Obj-C。

我看到许多应用程序(例如 Instagram,当图像标题具有“查看更多”时)使用 UICollectionView 实现了预期的结果而没有任何动画问题,所以我认为这一定是可能的。

【问题讨论】:

【参考方案1】:

如果不查看任何代码就很难诊断您的问题,因此这里有一个示例,证明使用UICollectionViewCompositionalLayoutUICollectionViewDiffableDataSource 可以实现具有平滑动画的垂直扩展单元格。

工作原理

有两个类值得注意:

    Cell:这是垂直扩展的UICollectionViewCellViewController:这是配置集合视图、数据源等的UIViewController

当通过collectionView(_:didSelectItemAt:) 选择Cell 时,items 数组中对应项的isExpanded 属性将切换,并且通过updateSnapshot() 更新数据源的快照:

代码

使用 Single View App 模板创建一个新的 Xcode 项目,并将此代码放入 ViewController.swift 文件中:

import UIKit

// MARK: - Cell -

final class Cell: UICollectionViewCell 
    static let reuseIdentifier = "Cell"

    var isExpanded = false 
        didSet  label.numberOfLines = numberOfLines 
    

    var numberOfLines: Int  isExpanded ? 0 : 3 

    lazy var label: UILabel = 
        let label = UILabel()
        label.numberOfLines = numberOfLines
        label.frame.size = contentView.bounds.size
        label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        return label
    ()

    override init(frame: CGRect) 
        super.init(frame: frame)
        contentView.addSubview(label)
    

    required init?(coder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    

    override func sizeThatFits(_ size: CGSize) -> CGSize 
        label.sizeThatFits(size)
    


// MARK: - UIViewController -

class ViewController: UIViewController 
    struct Item: Hashable 
        let text: String
        var isExpanded = false
        private let uuid = UUID()
    

    var items: [Item] = [
        .init(
            text: """
            Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
            incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
            nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
            Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore
            eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident.
            """
        ),
        .init(
            text: """
            Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
            incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
            nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
            Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore
            eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident.
            """,
            isExpanded: true
        )
    ]

    lazy var collectionView: UICollectionView = 
        let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createCollectionViewLayout())
        collectionView.register(Cell.self, forCellWithReuseIdentifier: Cell.reuseIdentifier)
        collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        collectionView.contentInset.top = 44
        collectionView.backgroundColor = .white
        collectionView.delegate = self
        return collectionView
    ()

    lazy var dataSource = UICollectionViewDiffableDataSource<Int, Item>(collectionView: collectionView)  collectionView, indexPath, itemIdentifier in
        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Cell.reuseIdentifier, for: indexPath) as? Cell else  fatalError() 
        cell.isExpanded = itemIdentifier.isExpanded
        cell.label.text = itemIdentifier.text
        return cell
    

    override func viewDidLoad() 
        super.viewDidLoad()
        view.addSubview(collectionView)
        updateSnapshot()
    

    private func createCollectionViewLayout() -> UICollectionViewCompositionalLayout 
        let layoutSize = NSCollectionLayoutSize.init(
            widthDimension: .fractionalWidth(1.0),
            heightDimension: .estimated(200)
        )

        let section = NSCollectionLayoutSection(group:
            .vertical(
                layoutSize: layoutSize,
                subitems: [.init(layoutSize: layoutSize)]
            )
        )
        section.contentInsets = .init(top: 0, leading: 16, bottom: 0, trailing: 16)
        section.interGroupSpacing = 20

        return .init(section: section)
    

    private func updateSnapshot() 
        var snapshot = NSDiffableDataSourceSnapshot<Int, Item>()
        snapshot.appendSections([0])
        snapshot.appendItems(items)
        dataSource.apply(snapshot, animatingDifferences: true)
    


// MARK: - UICollectionViewDelegate -

extension ViewController: UICollectionViewDelegate 
    public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) 
        guard let itemIdentifier = dataSource.itemIdentifier(for: indexPath) else  return 
        items[indexPath.row] = .init(text: itemIdentifier.text, isExpanded: !itemIdentifier.isExpanded)
        updateSnapshot()
    

【讨论】:

感谢您提供出色的答案和示例,我能够使用它达到我想要的结果。我还有一个问题,是否可以调整 diffable 动画?就我现在的情况而言,扩展单元格下方的内容有点太慢了,无法移开,并且在动画过程中瞬间出现了一些难看的重叠。 不客气!我认为使用apply(_:animatingDifferences:completion:) 时无法提供自定义动画。 @JWK 谢谢你的例子,但你知道为什么boundarySupplementaryItems 在折叠/展开动画中被隐藏了吗? @Carsten 我在这里回答了你的问题:***.com/questions/64790617/…

以上是关于UICollectionView 单元格展开动画问题的主要内容,如果未能解决你的问题,请参考以下文章

iOS:使用约束动画 UICollectionView 垂直扩展?

UICollectionView 单元格没有动画运动

UICollectionView 插入动画单元格立即出现,后面跟着实际的动画单元格

单元格删除时的 UICollectionView 动画

在 UICollectionView 单元格中停止动画

动画 UICollectionView 单元格大小更改和重新定位周围的单元格