iosswiftuicollectionviewios13cgaffinetransform

How do you animate NSCollectionLayoutVisibleItem for compositional UICollectionViewLayouts?


I'm trying out the new compositional layout APIs introduced with iOS13, they're pretty great - but I'm having an issue with one thing in particular and there's very little official documentation about it.

I'm looking to perform an animation on a NSCollectionLayoutVisibleItem object using visibleItemsInvalidationHandler. I've tried two different approaches so far:

  1. Using UIView property animators
layoutSection.visibleItemsInvalidationHandler = { (visibleItems, offset, env) in
    let normalizedOffsetX = offset.x + centeredPadding
    let centerPoint = CGPoint(x: normalizedOffsetX + collectionView.bounds.width / 2, y: 20)
    visibleItems.forEach({ item in
         UIView.animate(withDuration: 0.3) {
             item.transform = item.frame.contains(centerPoint) ? .identity : CGAffineTransform(scaleX: 0.9, y: 0.9)
         }
    })
}
  1. Using CATransaction animators
layoutSection.visibleItemsInvalidationHandler = { (visibleItems, offset, env) in
    let normalizedOffsetX = offset.x + centeredPadding
    let centerPoint = CGPoint(x: normalizedOffsetX + collectionView.bounds.width / 2, y: 20)
    visibleItems.forEach({ item in
        CATransaction.begin()
        CATransaction.setAnimationDuration(0.3)
        CATransaction.setCompletionBlock {
            item.transform = item.frame.contains(centerPoint) ? .identity : CGAffineTransform(scaleX: 0.9, y: 0.9)
        }
        CATransaction.commit()
    })
}

Neither of these approaches work - however, although not animated, I do see the transform changes reflected on the UI.


Solution

  • I've just discovered how to do this and hopefully this will help someone else. The key seems to be to use the indexPath property on the NSCollectionLayoutVisibleItem to then retrieve the UITableViewCell from the UICollectionView and perform any animations on it instead.

    Here's how I ended up solving my problem:

    layoutSection.visibleItemsInvalidationHandler = { (visibleItems, offset, env) in
        let normalizedOffsetX = offset.x + centeredPadding
        let centerPoint = CGPoint(x: normalizedOffsetX + collectionView.bounds.width / 2, y: 20)
        visibleItems.forEach({ item in
               guard let cell = collectionView.cellForItem(at: item.indexPath) else { return }
               UIView.animate(withDuration: 0.3) {
                    cell.transform = item.frame.contains(centerPoint) ? .identity : CGAffineTransform(scaleX: 0.9, y: 0.9)
               }
         })
    }