Wouter125 Wouter125 - 1 month ago 10
Swift Question

Keep active cell in collectionview in one place

I'm working on a tvOS application where I want the active cell of a collectionView always in the same position. In other words, I don't want the active cell to go through my collectionView but I want the collectionView to go through my active cell.

Example in the following image. The active label is always in the center of the screen while you can scroll the collectionView to get a different active label over there.

Basically a pickerview with a custom UI. But the pickerview of iOS is unfortunately not provided on tvOS...

Active Cell

I've already looked into

UICollectionViewScrollPosition.verticallyCentered
. That partially gets me there but it isn't enough. If I scroll really fast, the active item jumps further down the list and when I pause it scrolls up all the way to the center. I really want the active item to be in center of the screen at all times.

Any ideas on how to do this?




Update based on @HMHero answer

Okay, I tried to do what you told me, but can't get the scrolling to work properly. This is perhaps due to my calculation of the offset, or because (like you said)
setContentOffset(, animated: )
doesn't work.

Right now I did the following and not sure where to go from here;

Disable scrolling and center last and first label

override func viewDidLoad() {
super.viewDidLoad()
//Disable scrolling
self.collectionView.isScrollEnabled = false
//Place the first and last label in the middle of the screen
self.countryCollectionView.contentInset.top = collectionView.frame.height * 0.5 - 45
self.countryCollectionView.contentInset.bottom = collectionView.frame.height * 0.5 - 45
}


Getting the position of a label to retrieve the distance from the center of the screen (offset)

func collectionView(_ collectionView: UICollectionView, didUpdateFocusIn context: UICollectionViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
if let indexPath = context.nextFocusedIndexPath,
let cell = countryCollectionView.cellForItem(at: indexPath) {
//Get the center of the next focused cell, and convert that to position in the visible part of the (tv) screen itself
let cellCenter = countryCollectionView.convert(cell.center, to: countryCollectionView.superview)
//Get the center of the collectionView
let centerView = countryCollectionView.frame.midY
//Calculate how far the next focused cell y position is from the centerview. (Offset)
let offset = cellCenter.y - centerView
}
}


The offset returns incrementals of 100 when printing. The labels' height is 90, and there is a spacing of 10. So I thought that would be correct although it runs through all the way up to 2400 (last label).

Any ideas on where to go from here?

Answer Source

Okay, along with some pointers of @HMHero I've achieved the desired effect. It involved animating the contentOffset with a custom animation. This is my code;

func collectionView(_ collectionView: UICollectionView, didUpdateFocusIn context: UICollectionViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {

    if let previousIndexPath = context.previouslyFocusedIndexPath,
        let cell = collectionView.cellForItem(at: previousIndexPath) {
        cell.contentView.alpha = 0.3
        cell.contentView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
    }

    if let indexPath = context.nextFocusedIndexPath,
        let cell = collectionView.cellForItem(at: indexPath) {

        activeCell = cell
        activeCell.contentView.layer.anchorPoint = CGPoint(x:0,y: 0.5)


        let cellCenter = CGPoint(x: cell.bounds.origin.x + cell.bounds.size.width / 2, y: cell.bounds.origin.y + cell.bounds.size.height / 2)
        let cellLocation = cell.convert(cellCenter, to: self.collectionView)
        let centerView = collectionView.frame.midY
        let contentOffset = CGPoint(x: 0, y: cellLocation.y - centerView)

        UIView.animate(withDuration: 0.3, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
            collectionView.contentOffset = contentOffset
            self.activeCell.contentView.alpha = 1.0
            self.activeCell.contentView.transform = CGAffineTransform(scaleX: 1, y: 1)
        })
    }
}