UICollectionView单元格展开动画问题

UICollectionView Cell Expansion Animation Problem

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

这在没有动画的情况下工作正常,但是当我尝试为它设置动画时,如果扩展单元格下方的单元格最终位置超出屏幕边界,我无法让它看起来流畅,因为单元格将只是立即消失,而不是被动画 'pushed' 向下。可以在链接的 gif https://imgur.com/a/ulj0p6n

中看到该问题

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

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

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

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

如果不看任何代码就很难诊断您的问题,所以这里有一个例子证明可以使用 UICollectionViewCompositionalLayoutUICollectionViewDiffableDataSource.

实现垂直扩展的单元格和流畅的动画

工作原理

有两个类值得注意:

  1. Cell:这是垂直展开UICollectionViewCell.
  2. ViewController:这个是配置集合视图,数据源等的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()
    }
}