UICollectionViewCell - animate cell height change when its content changes

Viewed 1346

I have a really simple UICollectionView, using iOS 13's UICollectionViewCompositionalLayout. The cells have a vertical UIStackView, and when you press the "toggle details" button, I simple change the isHidden value of the detailsView. When the cells then inform the view controller that it needs to execute collectionView.collectionViewLayout.invalidateLayout(), everything basically works: the details view is toggled on and off. But it doesn't animate this, so it looks a bit jarring.

enter image description here enter image description here

class ViewController: UIViewController {
  @IBOutlet private var collectionView: UICollectionView!

  override func viewDidLoad() {
    super.viewDidLoad()
    collectionView.collectionViewLayout = createLayout()
  }

  private func createLayout() -> UICollectionViewLayout {
    let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(200))
    let item = NSCollectionLayoutItem(layoutSize: itemSize)

    let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(200))
    let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])

    let section = NSCollectionLayoutSection(group: group)
    section.contentInsets = .init(top: 16, leading: 16, bottom: 16, trailing: 16)
    section.interGroupSpacing = 16

    let layout = UICollectionViewCompositionalLayout(section: section)
    return layout
  }
}

extension ViewController: UICollectionViewDataSource {
  func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return 10
  }

  func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Cell
    cell.resize = { [weak self] in
      UIView.animate(withDuration: 2) {
        self?.collectionView.collectionViewLayout.invalidateLayout()
      }
    }
    return cell
  }
}

class Cell: UICollectionViewCell {
  @IBOutlet private var detailsView: UIView!

  var resize: (() -> Void)?

  override func awakeFromNib() {
    super.awakeFromNib()
    contentView.layer.cornerRadius = 10
  }

  @IBAction private func toggleDetails() {
    detailsView.isHidden.toggle()
    resize?()
  }
}

Repro project: https://github.com/kevinrenskers/CellHeight.

How can I animate the toggling of this details view, so that the height change animates? And can that be used in combination with the UIStackView and showing/hiding its children views? Please note that the details view doesn't have a fixed height in the real world app, so I can't just switch the cell height between two fixed values.

2 Answers

I believe that you are supposed to call collectionView.collectionViewLayout.invalidateLayout() outside of the animation block, and then call self?.collectionView.layoutIfNeeded() in the animation block.

With the collection view animated, I folk your repo and make some changes.

https://github.com/lebaotrung93/CellHeight

It works. But with UIStackView, It seems hard to manage the size of it in CollectionView due to its own behaviors.

Related