Earthling
Earthling

Reputation: 293

Centering UICollectionView focused row on tvOS

I have a UICollectionView in my tvOS application populated with several UICollectionViewCells, which in turn, contain a UICollectionView with items of their own inside.

The container UICollectionView scrolls vertically, whilst the inner UICollectionView scrolls horizontally. Focus is disabled for the top level UICollectionViewCells, and is passed into the items within the inner UICollectionView.

What it looks like, resembles this:

-------------------
|                 |
| --------------- |
| | xxxx xxxx xx| |
| | xxxx xxxx xx| |
| --------------- |
|                 |
| --------------- |
| | xxxx xxxx xx| |
| | xxxx xxxx xx| |
| --------------- |
|                 |
-------------------

What I'm trying to achieve here is when focus occurs on a row that is below the vertical centerline of the UICollectionView, the row should be vertically centered in the UICollectionView.

Before centering vertically
-------------------
| 1xxx xxxx xxxx x|
| 1xxx xxxx xxxx x|
|                 |
| 2xxx xxxx xxxx x|
| 2xxx xxxx xxxx x|
|                 |
| 3xxx xxxx xxxx x| <-
| 3xxx xxxx xxxx x| <-
|                 |
| 4xxx xxxx xxxx x|
-------------------

=

After centering vertically
-------------------
|                 |
| 2xxx xxxx xxxx x|
| 2xxx xxxx xxxx x|
|                 |
| 3xxx xxxx xxxx x|
| 3xxx xxxx xxxx x|
|                 |
| 4xxx xxxx xxxx x|
| 4xxx xxxx xxxx x|
|                 |
-------------------

Does anyone have a comment/suggestion on how I could achieve this?

Edit: Based on another SO post, I have tried to implement the following UICollectionViewDelegate function, but it does not seem to work for me:

func collectionView(_ collectionView: UICollectionView, didUpdateFocusIn context: UICollectionViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
        if let indexPath = context.nextFocusedIndexPath,
            let _ = collectionView.cellForItem(at: indexPath) {
            collectionView.contentInset.top = max((collectionView.frame.height - collectionView.contentSize.height) / 2, 0)
        }
    }

Edit: After some trial and error leading from the suggested answer below, I've gotten it to work with the following code:

func collectionView(_ collectionView: UICollectionView, didUpdateFocusIn context: UICollectionViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
        if let indexPath = context.nextFocusedIndexPath {
            collectionView.isScrollEnabled = false

            coordinator.addCoordinatedAnimations({
                self.collectionView.scrollToItem(at: indexPath, at: .centeredVertically, animated: true)
            }, completion: nil)
        }
    }

Upvotes: 3

Views: 1283

Answers (1)

Shahzaib Qureshi
Shahzaib Qureshi

Reputation: 920

You can use the scrollToItemAtIndexPath method like this :

func collectionView(_ collectionView: UICollectionView, didUpdateFocusIn context: UICollectionViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
    if let indexPath = context.nextFocusedIndexPath,
        let _ = collectionView.cellForItem(at: indexPath) {
        self.collectionView.scrollToItem(at: indexPath, at: 
         .centeredVertically, animated: true)
    }
}

EDITED

Some how you will have to extract the indexPath from the cell, using the accessiblity properties or something else, than you can scroll to that indexPath like this :

override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
    if let nextCell = context.nextFocusedView as? ScrumCollectionViewCell {
        let index = Int(nextCell.accessibilityHint!)!
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { // 2
            self.collectionView.scrollToItem(at: IndexPath(item: index, section: 0), at: .centeredVertically, animated: true)
        }
    }
}

Upvotes: 1

Related Questions