Earthling
Earthling

Reputation: 293

UICollectionView Horizontal Scrolling With Static Focused Item

I'm trying to create a horizontally-scrollable UICollectionView embedded within a UITableViewCell on tvOS, and have achieved my goal.

What I need to do next, is to be able to have the focus shift to the next item in the UICollectionView, but the whole stack of items should shift one position to the left or right, but the focus will remain at the 'first' item visually.

Here's a little ASCII diagram of what I'm hoping to achieve:

Scenario 1, before focus shifts
     ---------------------
1.   | v   v   v   v   v |
2.   |[v]  v   v   v   v |
3.   | v   v   v   v   v |
     ---------------------

Scenario 1, after focus shifts
     ---------------------
1.   | v   v   v   v   v |
2.   | v  [v]  v   v   v |
3.   | v   v   v   v   v |
     ---------------------

The above, Scenario 1, is the default behaviour, where the focus shifts to the next item in the UICollectionView.

Scenario 2, before focus shifts
     ---------------------
1.   | v   v   v   v   v |
2.   |[v]  v   v   v   v |
3.   | v   v   v   v   v |
     ---------------------

Scenario 2, after focus shifts
     ---------------------
1.   | v   v   v   v   v |
2. v |[v]  v   v   v     |
3.   | v   v   v   v   v |
     ---------------------

In Scenario 2, after I swipe on the remote to select the next item in the UICollectionView, the focus should remain on the left, but the item underneath the focus should shift by one.

Does anyone have any hints on how this can be achieved?

I have read posts on sticky columns in UICollectionView, but this is not exactly a sticky column, I think?

Upvotes: 2

Views: 1986

Answers (3)

Ihar Katkavets
Ihar Katkavets

Reputation: 1570

UICollectionViewDelegate methods are calling during focus movement. So to solve your task you need to operate UICollectionView's (UIScrollView) content offset. To implement like 'native' way you need:

Implement UICollectionViewDelegate method collectionView:didUpdateFocusInContext:withAnimationCoordiantor, save next focused cell origin (x,y), and in UIScrollViewDelegate method scrollViewWillEndDragging:withVelocity:targetContentOffset: set new target content offset which poinst to focused cell origin.

@interface CLViewController () <UICollectionViewDelegate, UICollectionViewDataSource>
@property (nonatomic) CGPoint focusedCellPosition;
@end

@implementation CLViewController
- (void)collectionView:(UICollectionView *)collectionView didUpdateFocusInContext:(UICollectionViewFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator {
    NSIndexPath *next = context.nextFocusedIndexPath;
    self.focusedCellPosition = [collectionView cellForItemAtIndexPath:next].frame.origin;
}

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
    *targetContentOffset = self.focusedCellPosition;
}
@end

Upvotes: 0

sivanesan Jegathas
sivanesan Jegathas

Reputation: 61

From tvOS 13 you can use the UICollectionLayoutSectionOrthogonalScrollingBehavior(.groupPagin) of UICollectionViewCompositionalLayout to handle this static focus for horizontal scroll. Just add one item in the group!

private func createCollectionViewLayout() -> UICollectionViewLayout {
        
    let sectionProvider = {
        (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
            let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
            let item = NSCollectionLayoutItem(layoutSize: itemSize)
            let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(100), heightDimension: .absolute(100))
            let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
            let section = NSCollectionLayoutSection(group: group)

            section.orthogonalScrollingBehavior = .groupPaging

            return section
        }
    return UICollectionViewCompositionalLayout(sectionProvider: sectionProvider)
}

Upvotes: 0

Lunarchaos42
Lunarchaos42

Reputation: 253

I had to do this for a project recently. Here is what I used:

 override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
    if let focusView = context.nextFocusedView as? UICollectionViewCell, let indexPath = collection.indexPath(for: focusView) {

        collection.isScrollEnabled = false
        coordinator.addCoordinatedAnimations({
            self.collection.scrollToItem(at: indexPath, at: .left, animated: true)
        }, completion: nil)
    }
}

Upvotes: 2

Related Questions