0

I am implementing a UICollectionViewCompositionalLayout and using

section.orthogonalScrollingBehavior = .groupPagingCentered

to scroll a section horizontally.

This lets a user scroll from page to page in a section horizontally.

I use

section.visibleItemsInvalidationHandler = {...}

to get the page a user scrolls to.

How can I scroll to a page in this section programmatically?

Gerd Castan
  • 6,275
  • 3
  • 44
  • 89
  • Unclear what your use of the invalidation handler is for, or what "preset programmatically" means. Configuring groups programmatically is the normal way. – matt May 18 '20 at 18:47
  • better to understand now? – Gerd Castan May 18 '20 at 18:50
  • 1
    Yes! I _think_ there is no way to do it. I understand the use of invalidation handler now too; it is just a workaround. Apple needs to give us a much better API for this. – matt May 18 '20 at 18:52

1 Answers1

0

I actually got it working. This code works XCode 12 Beta 2 / Target iOS14 / Swift 5.1.

In this piece of code, pay special attention to spacing, insets and orthogonalScrollingBehavior !!!

fileprivate func configureCollectionViewLayout() {
    func createLayout() -> UICollectionViewLayout {
        self.collectionView.canCancelContentTouches = false
        let layout = UICollectionViewCompositionalLayout {
            (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
            
            guard let sectionKind = Section(rawValue: sectionIndex) else { return nil }
            let spacing = CGFloat(10)

            switch sectionKind {
            case .someOtherSection:
                // configure the layout for this section
            case .yetAnotherSection:
                // configure the layout for this section
            case .sectionWithOrthogonalScrollingBehaviour:
                let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
                let item = NSCollectionLayoutItem(layoutSize: itemSize)
                let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalWidth(1.0*9.0/16.0))
                let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
                let section = NSCollectionLayoutSection(group: group)
                group.interItemSpacing = .fixed(0) // should (probably) be 0 otherwise there is an offset when scrollingToIndex
                section.interGroupSpacing = 0 // should (probably) be 0 otherwise there is an offset when scrollingToIndex
                section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 0, bottom: 0, trailing: 0) // leading and trailing should (certainly) be 0 otherwise the pages are offsetted in relation to the view
                section.orthogonalScrollingBehavior = .continuous // should be continous, otherwise it won't work
                return section
            }
        }
        return layout
    }
    collectionView.collectionViewLayout = createLayout()
}

To scroll programatically, I implemented a segmented control. When user taps on a segment, the orthogonally scrolling section scrolls automatically to the cell corresponding with the selected segment.

@IBAction func segmentedControlValueChanged(_ sender: UISegmentedControl) {
    
    // determine which Item belongs to the selected segment. In my case Items in a Section are represented by an enum called 'Item'

    let dataSourceIndexPath = self.dataSource.indexPath(for: .itemCase)
    let pressentationIndexPath = self.collectionView.presentationIndexPath(forDataSourceIndexPath: dataSourceIndexPath)! // This step is necessary, otherwise the next line does not have have wanted behaviour (I'm force unwrapping here to simplify. Don't do that in your final code)
    self.collectionView.scrollToItem(at: pressentationIndexPath, at: .centeredHorizontally, animated: true) // make sure you use .centeredHorizontally
}

I'm not sure this is how Apple intended this to be used. It might brake again in next Xcode beta versions.

Sidenote: In case you were wondering why I need/want a segmented control to scroll a collection view: It's because the content of the cells in the orthogonally scrolling section need to consume the horizontal pan gestures (the cells are all charts that you can move your finger along and then display the chart value at the touched location). Yes... I know this can also be done in other ways, but I wanted to use the compositional layout because it fits together nicely with the other sections, and I wanted to keep this sweet built in animation of the orthogonalscrollbehavior (even though this is now partially lost because orthogonalScrollingBehavior needs to be .continues for it to work) and I just wanted to see if it can be done :)

guido
  • 2,792
  • 1
  • 21
  • 40