NSCollectionView cell layout animation

I have an collectionView. I'm increasing it's height on selction. But, I want add animation on increasing height.


How do I achieve in sizeForItemAt method.


func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize {
        if let selectedRow = selectedRow {
            if selectedRow == indexPath.item {
                return NSSize(width: collectionView.bounds.width, height: 572)
            }
        }
        return NSSize(width: collectionView.bounds.width, height: 71)
    }

Accepted Reply

Here a complete code for the func. I scale x and y by a factor of 1.5, bring to front over other cells and keep the size at the end:


And on deselect, reset the original size


    func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set) {
       
        for indexPath in indexPaths { // { .first {
            if self.selectedRow != nil && self.selectedRow! == indexPath.item {
                self.selectedRow = nil
            } else {
                self.selectedRow = indexPath.item
            }
           
            if let cell = collectionView.item(at: indexPath.item) {
                CATransaction.begin()
                let animation = CABasicAnimation(keyPath: "transform.scale")
                animation.duration = 2.0
                animation.fromValue = 1.0
                animation.toValue = 1.5
                cell.view.layer!.zPosition = 1
                cell.view.layer?.add(animation, forKey: "transform.scale")
                cell.view.layer?.setAffineTransform( CGAffineTransform(scaleX: 1.5, y: 1.5)) //<<--- NEW CODE
                CATransaction.commit()
            }
        }
    }


    func collectionView(_ collectionView: NSCollectionView, didDeselectItemsAt indexPaths: Set) {
       
        for indexPath in indexPaths {
            let theItem = indexPath.item
           
            if let cell = collectionView.item(at: theItem) {
                CATransaction.begin()
                let animation = CABasicAnimation(keyPath: "transform.scale")
                animation.duration = 1.0
                animation.fromValue = 1.5
                animation.toValue = 1.0
                cell.view.layer!.zPosition = -1
                cell.view.layer?.add(animation, forKey: "transform.scale")
                cell.view.layer?.setAffineTransform( CGAffineTransform(scaleX: 1, y: 1))
                CATransaction.commit()
            }
        }
    }

Replies

Do the animation in didSelectItemAt.


Here is an example where you would just zoom the cell, without changing the size


    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {

        selectedCollectionItem = indexPath.row
        let cell = collectionView.cellForItem(at: indexPath)
        UIView.animate(withDuration: 3.0, delay: 0.0, options: .curveEaseOut, animations: {
            cell?.transform = cell!.isSelected ? CGAffineTransform(scaleX: 1.5, y: 1.5) : CGAffineTransform.identity
        }, completion: nil)

    }



Here a complete example I tested:


    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {

        selectedCollectionItem = indexPath.row
        let cell = collectionView.cellForItem(at: indexPath)
        UIView.animate(withDuration: 2.0, delay: 0.0, options: .curveEaseOut, animations: {
            cell?.layer.zPosition = cell!.isSelected ? 1 : -1
            cell?.transform = cell!.isSelected ? CGAffineTransform(scaleX: 1.5, y: 1.5) : CGAffineTransform.identity
        }, completion: { (done) in self.performSegue(withIdentifier: "ColorSegue", sender:self) })     // Once animated, can segue

    }

            selectedCollectionItem = indexPath.row
            UIView.animate(withDuration: 1.0, delay: 0.0, options: .curveEaseOut, animations: {
                cell?.layer.zPosition = cell!.isSelected ? 1 : -1
                cell?.transform = cell!.isSelected ? CGAffineTransform(scaleX: 1.5, y: 1.5) : CGAffineTransform.identity
            }, completion: nil)

        }

Hi Cluade,


I'm looking for NSCollectionView & not for UICollectionView

Yes I know ! But I tested on an existing project.


Should use CALayer and CABasicAnimation

let animation = CABasicAnimation(keyPath: "transform.scale.x")
animation.fromValue = 1
animation.toValue = 2


See Apple doc and

h ttp://www.knowstack.com/swift-mac-os-animation-part-1/

I tried both the ways, but it didn't work


option 1:

func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize {
        
        if let selectedRow = selectedRow {
            if selectedRow == indexPath.item {
                CATransaction.begin()
                
                let animation = CABasicAnimation(keyPath: "transform.scale.y")
                animation.duration = 3.0
                animation.fromValue = 71
                animation.toValue = 571
                collectionView.item(at: indexPath)?.view.layer?.add(animation, forKey: "transform.scale.y")
                
                CATransaction.commit()
                return NSSize(width: collectionView.bounds.width, height: 571)

            }
        }
        return NSSize(width: collectionView.bounds.width, height: 71)
        
    }


option 2:

func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set<IndexPath>) {
        
        if let indexPath = indexPaths.first {

            if self.selectedRow == indexPath.item {
                self.selectedRow = nil
            } else {
                self.selectedRow = indexPath.item
            }
            
            if let cell = collectionView.item(at: selectedRow!) {
                CATransaction.begin()
                
                let animation = CABasicAnimation(keyPath: "transform.scale.y")
                animation.duration = 2.0
                animation.fromValue = 71
                animation.toValue = 571
                cell.view.layer?.add(animation, forKey: "transform.scale.y")
                
                CATransaction.commit()
            }

        }
        
    }

I would not hope option 1 to work.


On option 2, when you say it does not work, what do you mean :

- it crashes ?

- it does not show animation

- it shows a wrong animation


I would try removing

CATransaction.begin()

and

CATransaction.commit()


Please add a log line 10

print("selectedRow is", self.selectedRow)

Animation didn't work.


Tried removing:

CATransaction.begin()

and

CATransaction.commit()


still didn't work

You mean: code runs, but no animation shown ?

Yes

I have built a project and think I found out the problem.


It is your scale factor:

                animation.fromValue = 71
                animation.toValue = 571


It must be a scale, not absolute value.


Change for instance to 1.0 and 2.0

                animation.fromValue = 1.0
                animation.toValue = 2.0

It will work.


If you want to keep the new size at the end of animation, do something like this just before commit:

                let newSize = CGSize(width: cell.view.frame.width * 2, height: cell.view.frame.height * 2)   // 2 as example; find the appropriate value
                cell.view.setFrameSize(newSize)

Here a complete code for the func. I scale x and y by a factor of 1.5, bring to front over other cells and keep the size at the end:


And on deselect, reset the original size


    func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set) {
       
        for indexPath in indexPaths { // { .first {
            if self.selectedRow != nil && self.selectedRow! == indexPath.item {
                self.selectedRow = nil
            } else {
                self.selectedRow = indexPath.item
            }
           
            if let cell = collectionView.item(at: indexPath.item) {
                CATransaction.begin()
                let animation = CABasicAnimation(keyPath: "transform.scale")
                animation.duration = 2.0
                animation.fromValue = 1.0
                animation.toValue = 1.5
                cell.view.layer!.zPosition = 1
                cell.view.layer?.add(animation, forKey: "transform.scale")
                cell.view.layer?.setAffineTransform( CGAffineTransform(scaleX: 1.5, y: 1.5)) //<<--- NEW CODE
                CATransaction.commit()
            }
        }
    }


    func collectionView(_ collectionView: NSCollectionView, didDeselectItemsAt indexPaths: Set) {
       
        for indexPath in indexPaths {
            let theItem = indexPath.item
           
            if let cell = collectionView.item(at: theItem) {
                CATransaction.begin()
                let animation = CABasicAnimation(keyPath: "transform.scale")
                animation.duration = 1.0
                animation.fromValue = 1.5
                animation.toValue = 1.0
                cell.view.layer!.zPosition = -1
                cell.view.layer?.add(animation, forKey: "transform.scale")
                cell.view.layer?.setAffineTransform( CGAffineTransform(scaleX: 1, y: 1))
                CATransaction.commit()
            }
        }
    }