iosuicollectionviewuicollectionviewlayoutuicollectionviewdelegate

Animate changes in UICollectionViewFlowLayout


I'm using a UICollectionViewFlowLayout to arrange view tiles in a horizontally scrolling view. By default, I make them square using the UICollectionViewDelegateFlowLayout:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    var nominalSize = CGSize(width: collectionView.frame.height, height: collectionView.frame.height)
    return nominalSize
}

This way I get square tiles, sized to whatever the current of the collectionView is. However, there is a condition where I modify that nominal size for some tiles, so it evolves a little to look like:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    var nominalSize = CGSize(width: collectionView.frame.height, height: collectionView.frame.height)
    if someTest(indexPath) {
        nominalSize = self.tweakNominalSize(nominalSize)
    }
    return nominalSize
}

What I haven't figured out is how to trigger the collection view to A) recall the delegate to relayout and B) how to make it animate the changes.

What I have tried:

1)

self.myCollectionView.setNeedsLayout()
UIView.animateWithDuration(200.milliseconds) {
    self.myCollectionView.layoutIfNeeded()
}

This does nothing. print() statements in the delegate callback reveal that it is never called.

2)

self.schedulesView.setCollectionViewLayout(
    self.schedulesView.collectionViewLayout,
    animated: true)

This also does nothing. I'm not sure if its being clever or what. Maybe it's smart enough to note that they're identical and opt out?

3)

self.schedulesView.startInteractiveTransition(
    to: self.schedulesView.collectionViewLayout,
    completion: nil)
self.schedulesView.finishInteractiveTransition()

This does cause the layout to happen! But not animated at all. It just suddenly changes. It seems to keep the right side of the tile where it was, rather than the left AND it seems to happen distinctly after other UI changes I effected between the two calls.


Solution

  • Your collectionView(_:layout:sizeForItemAt:) looks correct to me (I have something similar).

    You need to call performBatchUpdates(_:completion:) AFTER you make your changes and BEFORE you calllayoutIfNeeded`.

    // Some call that makes your call to `someTest()` in `collectionView(_:layout:sizeForItemAt:)` return what you want
    myCollectionView.performBatchUpdates(nil, completion: nil)
    myCollectionView.layoutIfNeeded()
    

    It's not necessary to call myCollectionView.setNeedsLayout(), as performBatchUpdates(_,completion) will set that for you.