6

i have a collectionview that i am trying to scroll programatically. the problem being that if the cell i want to scroll to is visible in the collection view it doesn't scroll it to the centre. so for the image below the lower cell is item 1. and it does not scroll to it but it will scroll past item 1 to item 2.

i have been trying to use UICollectionVieScrollPosition.CenterVertically but this does not seem to work.

self.collectionView?.scrollToItem(at: IndexPath(row: 1, section: 0), at: UICollectionViewScrollPosition.centeredVertically, animated: true)

is there a way around this to force the scrolling of cells that are visible to the centre of the collection?

enter image description here

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Pippo
  • 1,439
  • 1
  • 18
  • 35
  • On what event you are scrolling it? – Dheeraj D Jan 01 '17 at 04:06
  • I'm using a custom flow layout to make the cells scale and sticky so when scrolling it always "snaps" to the centre which is using offsets. it works all the time just not when scrolling programatically to an item that is visible in the view – Pippo Jan 01 '17 at 04:07
  • its scrolling from viewDidLoad after getting a bunch of data from the server – Pippo Jan 01 '17 at 04:08

5 Answers5

24

the best way i found to do this is to not use scrollToItem but to get the CGRect of the index and then make that visible.

     let rect = self.collectionView.layoutAttributesForItem(at: IndexPath(row: 5, section: 0))?.frame
     self.collectionView.scrollRectToVisible(rect!, animated: false)
Pippo
  • 1,439
  • 1
  • 18
  • 35
  • 3
    Voted. For me ```scrollToItem``` doesn't scroll to the position I want and also have some layout problems(make collectionview re-layout sometimes with crazy cell rearrange). This approach works pretty reliable. But anyone knows why? – code4latte Jan 24 '19 at 23:45
  • you may be using a custom `UICollectionViewLayout` in that case get the rect by calling `let rect = self.collectionView.collectionViewLayout.layoutAttributesForItem(at: indexPathToSelect)?.frame`. the scrollToVisible(indexPath:) method of uicollectionview does not work well for me when items are far off screen. this approach works. – Samuël Jun 02 '20 at 15:25
  • You save my day. I tried a lot of things and this was the only solution – Sahan Sandeepa Nagodavithana Jan 21 '21 at 09:43
5

I'm trying to delay it with 0.1s. For my case, looks good for now:

collectionView.collectionViewLayout.invalidateLayout() //just in case, iOS10 may crash btw.

DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
    self.collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
}

Update:

ok, turns out that I'm using layout.estimatedItemSize and autolayout to calculate the width of my cells, that's why I have this problem. That says, for me, it's because of CollectionView's dynamic sizing. After I back to calculate the width manually, everything works fine. (by using -collectionView:layout:sizeForItemAt:)

boog
  • 1,813
  • 3
  • 18
  • 21
  • 1
    As [the document](https://developer.apple.com/documentation/dispatch/dispatchqueue/2300020-asyncafter) said, you can use the `DispatchQueue.main.async{ /* scrollToItem */ }` instead, and it works well for me . – Dennis Lee Sep 04 '20 at 02:17
0

First of all you need to find the center of the CollectionView and this is how it can be done:

private func findCenterIndex() -> Int {
        let center = self.view.convert(numberCollectionView.center, to: self.numberCollectionView)
        let row = numberCollectionView!.indexPathForItem(at: center)?.row


        guard let index = row else {

            return 0
        }


        return index
    }

then get the row number under your scrollToItem and it should work:

collectionView.scrollToItem(at: IndexPath(item: findCenterIndex(), section: 0), at: .centeredVertically, animated: false)

call it from viewDidLayoutSubviews()

Fay007
  • 2,707
  • 3
  • 28
  • 58
  • I can see how this gets the index for the centre item. i can not see how this will scroll item to centre and i am unclear what you mean by call function under item. you are correct that it should be indexPath(item: and not (row: – Pippo Jan 01 '17 at 05:07
  • it is an elegant way of returning the centre index, but i don't know what your thinking, its obvious to you, how this will scroll an indexed item to the centre but i can not see it. – Pippo Jan 01 '17 at 06:17
  • this command will tell your collection view to scroll at a item position. So if your center is at the item 3 where your total items are 6 then it should scroll to it. – Fay007 Jan 01 '17 at 06:54
0

If itemSize is too small,scrollToItemnot working. siwft collectionView.contentOffset = offset I use this fixed

黄伯驹
  • 11
  • 2
0

It seems that you have centred the first cell in your UICollectionView vertically.

I have found that if I centred the first cell by adding an inset through the contentInset property of UICollectionView, its scrollToItem(at:at:animated:) method also doesn't work for cells already visible.

However, if I centred the first cell by adding an inset through the sectionInset property of UICollectionViewFlowLayout, then scrollToItem(at:at:animated:) works for visible cells.

Specifically, in code:

override func viewDidLayoutSubviews() {

    super.viewDidLayoutSubviews()

    // Padding needed to allow the first and last cells to be centred vertically.
    let insectHeight = (collectionView.bounds.height - collectionViewFlowLayout.itemSize.height) / 2.0

    // This way is disabled because scrollToItem doesn't work for visible cells.
    // collectionView.contentInset = UIEdgeInsets(top:    insectHeight,
    //                                            left:   0,
    //                                            bottom: insectHeight,
    //                                            right:  0)

    // This is the way for scrollToItem to work for visible cells.
    collectionViewFlowLayout.sectionInset = UIEdgeInsets(top:    insectHeight,
                                                         left:   0,
                                                         bottom: insectHeight,
                                                         right:  0)

}

My guess is that contentInset is a property UICollectionView inherited from UIScrollView, and the way scrollToItem(at:at:animated:) works out the offset seems incompatible to the way contentInset is used by UIScrollView.

dev.ij
  • 1
  • 1