Search code examples
swiftuicollectionviewuicollectionviewcelltvos

tvOS/Swift 3: How fix broken UICollectionView selection animation?


I have subclassed UICollectionViewCell so that a parallax effect can be applied to the whole cell rather than just the image in the cell. The parallax effect works as I want it to, but it breaks the push button like animation that happens when the remote button is pressed on a selected cell.

collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) is called as it should be the the remote button, there's just not any animation.

I presume I have to do the animation, but don't know how to trigger it, or, indeed, how to do it.

This is my UICollectionViewCell:

class CollectionViewCellA: UICollectionViewCell {

@IBOutlet var image: UIImageView!
@IBOutlet var bkgImage: UIImageView!
@IBOutlet var title: UILabel!

override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {

    struct wrapper {
        static let s_atvMotionEffect = UIAppleTVMotionEffectGroup()
    }

    coordinator.addCoordinatedAnimations( {
        var scale : CGFloat = 0.0
        if self.isFocused {
            self.addMotionEffect(wrapper.s_atvMotionEffect)
            self.layer.shadowOpacity = 0.75;
            self.layer.shadowRadius = 32.0;
            self.layer.shadowOffset = CGSize(width: 0, height: 16);
            scale = 1.2
        } else {
            self.removeMotionEffect(wrapper.s_atvMotionEffect)
            self.layer.shadowOpacity = 0.25;
            self.layer.shadowRadius = 4.0;
            self.layer.shadowOffset = CGSize(width: 0, height: 0);
            scale = 1.0
        }
        let transform = CGAffineTransform(scaleX: scale, y: scale)
        self.layer.setAffineTransform(transform)
    },completion: nil)

   }
}

This the UIViewController where the UICollectionView resides:

class HomeVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {


    @IBOutlet var freeContentCV: UICollectionView!
    @IBOutlet var collectionViewB: UICollectionView!
    @IBOutlet var scrollView: UIScrollView!
    @IBOutlet var mainImage: UIImageView!

    override func viewDidLoad() {
        super.viewDidLoad()
        freeContentCV.delegate = self
        collectionViewB.delegate = self

        freeContentCV.dataSource = self
        collectionViewB.dataSource = self
    }


    override func viewDidLayoutSubviews() {
        self.scrollView!.contentSize = CGSize(width: 1920, height: 2500)
    }

    override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
        //print(UIScreen.main.focusedView)
    }

    // Collection View Methods

    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

        if collectionView == self.freeContentCV{
        return 10
       }

     return 10
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        if collectionView == self.freeContentCV {
        let cellA = collectionView.dequeueReusableCell(withReuseIdentifier: "CellA", for: indexPath) as! CollectionViewCellA
        cellA.bkgImage.image = UIImage(named: "vincent")
        cellA.title.text = "TITLE"
        return cellA
        }

        let cellB = collectionView.dequeueReusableCell(withReuseIdentifier: "CellB", for: indexPath) as! CollectionViewCellA
        cellB.image.image = UIImage(named: "vincent")
        cellB.title.text = "TITLE"
        return cellB
    }


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

       // What goes here to trigger an animation, if anything? 
    }

}

I presume the didSelectItemAt function is where I have to trigger the push-button animation, but I don't know how to access the UICollectionViewCell to animate it, or even if that's possible with the arrangement I have. At the edge of my iOS/tvOS understanding here, so I am not sure of the right question(s) to ask.

I've looked at many sources and have not yet found any discussion of this issue.

Any ideas greatfully received.


Solution

  • After a fresh crack at the problem this morning, here's what works.

    In the UICollectionViewCell

    class CollectionViewCellA: UICollectionViewCell {
    
    @IBOutlet var image: UIImageView!
    @IBOutlet var bkgImage: UIImageView!
    @IBOutlet var title: UILabel!
    
    var selectTrans: UIFocusAnimationCoordinator?
    var scale : CGFloat = 0.0
    
    override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
    
        struct wrapper {
            static let s_atvMotionEffect = UIAppleTVMotionEffectGroup()
        }
    
        coordinator.addCoordinatedAnimations( {
            if self.isFocused {
                self.addMotionEffect(wrapper.s_atvMotionEffect)
                self.layer.shadowOpacity = 0.75;
                self.layer.shadowRadius = 10.0;
                self.layer.shadowOffset = CGSize(width: 0, height: 5);
                self.scale = 1.2
                let transform = CGAffineTransform(scaleX: self.scale, y: self.scale)
                self.layer.setAffineTransform(transform)
            } else {
                self.removeMotionEffect(wrapper.s_atvMotionEffect)
                self.layer.shadowOpacity = 0.25;
                self.layer.shadowRadius = 4.0;
                self.layer.shadowOffset = CGSize(width: 0, height: 0);
                self.scale = 1.0
                let transform = CGAffineTransform(scaleX: self.scale, y: self.scale)
                self.layer.setAffineTransform(transform)
            }
        },completion: nil)
    
    }
    
    func startSelectAnimation() {
        scale = 1.1
        let transform = CGAffineTransform(scaleX: scale, y: scale)
        self.layer.setAffineTransform(transform)
        perform(#selector(middleAnimation), with: nil, afterDelay: 0.08)
    }
    
    func middleAnimation() {
        scale = 0.95
        let transform = CGAffineTransform(scaleX: scale, y: scale)
        self.layer.setAffineTransform(transform)
        perform(#selector(endSelectAnimation), with: nil, afterDelay: 0.1)
    }
    
    func endSelectAnimation(){
        scale = 1.2
        let transform = CGAffineTransform(scaleX: scale, y: scale)
        self.layer.setAffineTransform(transform)
    }
    }
    

    In the ViewController

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    
             // We have to provide animation, so get the cell and do call to animation
             let selectedCell = collectionView.cellForItem(at: indexPath) as! CollectionViewCellA
             selectedCell.startSelectAnimation()
             if collectionView == self.freeContentCV {
             performSegue(withIdentifier: "ShowVideoDetail", sender: self)
         }
        }
    

    The animation is a little clunky, but when it triggers a segue the jerkiness is not all that noticeable.