Search code examples
iosswiftanimationuicollectionviewuistackview

In UICollectionView, collapse animation is imperfect, with no height shrinking animation for the hidden item


Based on the problem described in

How to achieve smooth expand/ collapse animation in UICollectionView with dynamic cell height?

and the improvement mentioned in

Change default StackView animation

I had made the following changes

  1. Added an inner UIView, for collapse and expand purpose
  2. The inner UIView will have attribute Clips to Bounds set to true
  3. A vertical UIStackView placed in the inner UIView, with bottom constraint set to high.
  4. I really don't know. For unknown reason, the bottom constraint of the parent UIStackView need to be low, in order for the UI to stay at top, during collapse animation.

This is my outcome.


enter image description here

As you can see,

  1. The expand animation works just fine!
  2. For collapse animation, the inner UIView will disappear immediately, without any animation.

Do you have any idea, why there is no hidden animation during collapse?

Here's the code which perform collapse/ expand.

extension ViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return shops.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        guard let collectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? CollectionViewCell else {
            fatalError()
        }
        
        let shop = shops[indexPath.item]
        
        collectionViewCell.title.text = shop.title
        collectionViewCell._description.text = shop.description
        
        if isExpanded[indexPath.item] {
            collectionViewCell.innerView.isHidden = false
        } else {
            collectionViewCell.innerView.isHidden = true
        }
        
        return collectionViewCell
    }

}
extension ViewController: UICollectionViewDelegate {
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        for i in (0..<isExpanded.count) {
            if i == indexPath.item {
                // ensure always visible
                isExpanded[i] = true
            } else {
                // set all other rows to false
                isExpanded[i] = false
            }
            if let c = collectionView.cellForItem(at: IndexPath(item: i, section: 0)) as? CollectionViewCell {
                c.innerView.isHidden = !isExpanded[i]
            }
        }
        collectionView.performBatchUpdates(nil, completion: nil)
    }
}

What I have tried so far is

  1. Use a zero height constraint to activate/ deactivate, to replace isHidden
  2. Use UIView.animate

But, the inner UIView will disappear immediately, without any height shrinking animation.

Do you have idea how I can fix this? Thanks.

Here's the code to illustrate the problem - https://github.com/yccheok/shop-dialog/tree/c399bca163096ad27de7de866af5d2de370a8afb


Solution

  • As I mentioned in a comment to you on another question, rarely do we find a "one size fits all" solution.

    Instead of fighting with the default behaviors when setting .isHidden on a stack view's arranged subviews, here's a different approach.

    Use two bottom constraints:

    • One from the bottom of the "top / always-visible" UI elements
    • One from the bottom of the "show/hide" elements (their container view)

    Set the .priority of the second constraint to 750 (.defaultHigh).

    When you want the cell to be "collapsed" set the .priority of the first constraint to 751 (.defaultHigh + 1).

    When you want the cell to be "expanded" set the .priority of the first constraint to 749 (.defaultHigh - 1).

    To animate the expand/collapse effect, wrap performBatchUpdates in a UIView.animate block.

    I forked your GitHub repo here - https://github.com/DonMag/shop-dialog - and added this approach as "V2" so you can inspect it and see the differences.