Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to animate a UICollectionView cell selection

What does the animated argument of these methods of UICollectionView do:

  • selectItem(at indexPath: IndexPath?, animated: Bool, scrollPosition: UICollectionViewScrollPosition)`
    
  • deselectItem(at indexPath: IndexPath, animated: Bool)
    

I know I can use the UICollectionViewLayout object to animate changes. I also know that I can use didSelect and didDeselect methods of the UICollectionViewDelegate to get the selected cell and apply animations. But I can't find any information about how the above animated argument affects animations. Does it affect the layout animations in any way? My goal is to create a UICollectionView subclass and allow the consumer to customize whether animations apply when the above two methods are called internally. But I don't know what animations those methods control.

like image 647
Hash88 Avatar asked Mar 13 '18 17:03

Hash88


2 Answers

The animated in UICollectionView's selectItem(at:animated:scrollPosition:) determines whether the item to-be-selected, if not in view or at required position already, should be scrolled to in an animated fashion or not.
If it's in view then this animated property doesn't really do anything, afaik.
Same for the animated in deselectItem(at:animated:). It doesn't do anything and is simply there.

The only thing I see affecting the layout engine is if the collectionView scrolls and you have animations in the didSelectItemAt then it will render these animations ineffective. You would have to delay the animations occurring in the cell (see last example in this answer)


As you already know but for others, if you want to animate the cell selection event then you will have to do it yourself in the collectionView(_:didSelectItemAt:) delegate.

Example:

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    let cell = collectionView.cellForItem(at: indexPath)

    //Briefly fade the cell on selection
    UIView.animate(withDuration: 0.5,
                   animations: {
                    //Fade-out
                    cell?.alpha = 0.5
    }) { (completed) in
        UIView.animate(withDuration: 0.5,
                       animations: {
                        //Fade-out
                        cell?.alpha = 1
        })
    }

}

The above is fine if the user taps on a cell but if you programmatically call selectItem(at:animated:scrollPosition:), it won't trigger the above collectionView(_:didSelectItemAt:) delegate and you would need to explicitly call it to run your selection animation.

Example (Add-on to previous):

func doSelect(for aCollectionView: UICollectionView,
              at indexPath: IndexPath) {
    aCollectionView.selectItem(at: indexPath,
                               animated: true,
                               scrollPosition: .centeredVertically)

    //DispatchQueue after sometime because scroll animation renders
    //the animation block in `collectionView(_:didSelectItemAt:)` ineffective
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.27) { [weak self] in
        self?.collectionView(aCollectionView,
                             didSelectItemAt: indexPath)
    }
}
like image 69
staticVoidMan Avatar answered Nov 04 '22 01:11

staticVoidMan


I would suggest overriding isHighlighted property of UICollectionViewCell with animation. That way if a user taps on a cell and stops to think what to do next, the animation state will be preserved.

    override var isHighlighted: Bool {
        didSet {
            toggleIsHighlighted()
        }
    }

    func toggleIsHighlighted() {
        UIView.animate(withDuration: 0.1, delay: 0, options: [.curveEaseOut], animations: {
            self.alpha = self.isHighlighted ? 0.9 : 1.0
            self.transform = self.isHighlighted ?
                CGAffineTransform.identity.scaledBy(x: 0.97, y: 0.97) :
                CGAffineTransform.identity
        })
    }
like image 33
Alexander Martirosov Avatar answered Nov 04 '22 03:11

Alexander Martirosov