UICollectionView can't keep the focused row stick to the top

I'm creating a Netflix like app on tvOS, here is my compositional layout that I use with my collectionView.

private func createLayout() -> UICollectionViewCompositionalLayout {
    return UICollectionViewCompositionalLayout { (section, _) -> NSCollectionLayoutSection? in
        // item
        let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
                                              heightDimension: .fractionalHeight(1.0))
        let item = NSCollectionLayoutItem(layoutSize: itemSize)
        item.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10)
        
        // group
        let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1/5),
                                               heightDimension: .estimated(350))
        let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
        
        // section
        let section = NSCollectionLayoutSection(group: group)
        section.orthogonalScrollingBehavior = .groupPaging
        
        
        let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                heightDimension: .estimated(UIFont(name: "MyFont", size: 36)!.lineHeight))
        let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
            layoutSize: headerSize,
            elementKind: "SectionHeader",
            alignment: .top)
        
        section.boundarySupplementaryItems = [sectionHeader]
        section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 20, bottom: 30, trailing: 0)
        
        return section
    }
}

So:

  • My collection view is composed by several row (that are the sections).
  • Each section is composed by groups containing 1 item each.
  • Each section can be scrolled horizontally.
  • And you can scroll vertically to move from a section to an other.

I'm expecting to have the following behavior when scrolling: Horizontal scrolling:

Before focus shifts

--------------------------
|  vA    vB     vC   vD   vE  |
| [v1]   v2     v3   v4   v5   |
|  vU    vW   vX   vY   vZ   |
--------------------------

After focus shifts

--------------------------
|  vA    vB   vC   vD   vE  | 
| [v2]  v3    v4   v5   v6  |
|  vU   vW   vX   vY   vZ  |
--------------------------

Vertical scroll:

Before focus shifts

--------------------------
| [vA]  vB   vC   vD   vE  |
|  v1    v2    v3   v4   v5  |
|  vU   vW   vX   vY   vZ  |
--------------------------

After focus shifts

--------------------------
| [v1]  v2   v3   v4   v5  |
|  vU   vW  vX   vY  vZ  |
|  vF   vG   vH   vI   vJ   |
--------------------------

Thanks to section.orthogonalScrollingBehavior = .groupPaging the horizontal scrolling is working as expected. (I've put 1 item per group to achieve this)

But I'm going crazy with the vertical scroll, I'm not able to achieve it as expected, the focused section still centered verticaly on the screen ! What I'm getting:

Before focus shifts

--------------------------
| [vA]  vB   vC   vD   vE |
|  v1    v2   v3   v4   v5  |
|  vU   vW  vX   vY   vZ  |
--------------------------

After focus shifts

--------------------------
| vA   vB   vC   vD   vE  |
| [v1]  v2   v3   v4   v5  |
| vU   vW   vX   vY   vZ |
--------------------------

After an other focus shifts

--------------------------
|   v1    v2   v3   v4   v5 |
| [vU]  vW  vX   vY   vZ |
|  vF    vG   vH   vI   vJ  |
--------------------------

I've tried to play with the didUpdateFocusIn function without success

func collectionView(_ collectionView: UICollectionView, didUpdateFocusIn context: UICollectionViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
    if let nextFocusedItemIndexPath = context.nextFocusedIndexPath {
        let section = nextFocusedItemIndexPath.section
        let sectionIndexPath = IndexPath(item: 0, section: section)
        collectionView.scrollToItem(at: sectionIndexPath, at: .top, animated: true)
    }
}

The collectionview is behaving strangely when using the didUpdateFocusIn function and I feel it's not the good way to perform what I'm expecting...

I've also try to play with the scrollViewWillEndDragging function without success

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: 
}

Everything that I tried looks like a hack and I can't believe Apple has not plan something for this use case, which is in my sense a common use case....

Answered by DTS Engineer in 798503022

@Seb_STR you can check the value of delegate method canFocusItemAtIndexPath or shouldUpdateFocusInContext and scroll to the particular section.

For example:

func collectionView(_ collectionView: UICollectionView, canFocusItemAt indexPath: IndexPath) -> Bool {
        let firstItemInSectionIndexPath = IndexPath(item: 0, section: indexPath.section)
        collectionView.scrollToItem(at: firstItemInSectionIndexPath, at: .top, animated: true)
        return true
    }

@Seb_STR you can check the value of delegate method canFocusItemAtIndexPath or shouldUpdateFocusInContext and scroll to the particular section.

For example:

func collectionView(_ collectionView: UICollectionView, canFocusItemAt indexPath: IndexPath) -> Bool {
        let firstItemInSectionIndexPath = IndexPath(item: 0, section: indexPath.section)
        collectionView.scrollToItem(at: firstItemInSectionIndexPath, at: .top, animated: true)
        return true
    }
UICollectionView can't keep the focused row stick to the top
 
 
Q