109

Before loading the collection view user sets the number of image in the array of collection view. All of the cells don't fit on the screen. I have 30 cells and only 6 on the screen.

The question: How to scroll to the cell with desired image automatically at the load of UICollectionView?

AlexanderZ
  • 2,020
  • 3
  • 18
  • 23

9 Answers9

175

New, Edited Answer:

Add this in viewDidLayoutSubviews

SWIFT

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    let indexPath = IndexPath(item: 12, section: 0)
    self.collectionView.scrollToItem(at: indexPath, at: [.centeredVertically, .centeredHorizontally], animated: true)
}

Normally, .centerVertically is the case

ObjC

-(void)viewDidLayoutSubviews {
   [super viewDidLayoutSubviews];
    NSIndexPath *indexPath = [NSIndexPath indexPathForItem:12 inSection:0];
   [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredVertically | UICollectionViewScrollPositionCenteredHorizontally animated:NO];
}

Old answer working for older iOS

Add this in viewWillAppear:

[self.view layoutIfNeeded];
[self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:NO];
boweidmann
  • 3,146
  • 2
  • 16
  • 26
  • 4
    That's exactly what I'm trying to use. I have the number of the object in the array and I know the name of that object. But don't know how to put correct indexPath... Any ideas? – AlexanderZ Apr 13 '13 at 17:55
  • 4
    You can easily create `indexPath`: `[NSIndexPath indexPathForItem:numberOfTheObject inSection:sectionNumber]` – boweidmann Apr 13 '13 at 17:59
  • 3
    Had some errors. But then I've put all the mentioned code inside (void)viewDidLayoutSubviews{} and now it's working fine. Thanx Bogdan. – AlexanderZ Apr 13 '13 at 18:39
  • 1
    How can I 'zoom' into the cell, and show it magnified? – fatuhoku Apr 23 '14 at 12:13
  • 9
    This doesn't work reliably on iOS 7.1 and can lead to the collection view appearing as a black screen. I also didn't want to maintain state like the solution in viewDidLayoutSubviews below. What I did is simply performed a [self.view layoutIfNeeded] right before the call to scrollToItemAtIndexPath – Daniel Galasko Nov 21 '14 at 07:19
  • Thanks @DanielGalasko, going to adjust the answer – boweidmann May 16 '15 at 07:20
  • `viewWillAppear` it doesn't work. add it on `viewDidLayoutSubviews` – Antony Raphel Jun 15 '17 at 06:11
  • writing these lines in some other method. It's not working – Mansuu.... Nov 07 '17 at 12:43
  • those who have horizontal collectionview should use UICollectionViewScrollPositionCenteredHorizontally – birdcage Apr 18 '18 at 11:16
  • sir how to put the indexpath in paramter in collectionView.scrollToItem – Gorib Developer Jul 14 '18 at 08:27
99

I've found that scrolling in viewWillAppear may not work reliably because the collection view hasn't finished it's layout yet; you may scroll to the wrong item.

I've also found that scrolling in viewDidAppear will cause a momentary flash of the unscrolled view to be visible.

And, if you scroll every time through viewDidLayoutSubviews, the user won't be able to manually scroll because some collection layouts cause subview layout every time you scroll.

Here's what I found works reliably:

Objective C :

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    // If we haven't done the initial scroll, do it once.
    if (!self.initialScrollDone) {
        self.initialScrollDone = YES;

        [self.collectionView scrollToItemAtIndexPath:self.myInitialIndexPath
                                    atScrollPosition:UICollectionViewScrollPositionRight animated:NO];
    }
}

Swift :

 override func viewWillLayoutSubviews() {

    super.viewWillLayoutSubviews()

      if (!self.initialScrollDone) {

         self.initialScrollDone = true
         self.testNameCollectionView.scrollToItem(at:selectedIndexPath, at: .centeredHorizontally, animated: true)
    }

}
amp.dev
  • 251
  • 2
  • 13
LenK
  • 2,684
  • 1
  • 17
  • 22
  • Nice tip. I'll emphasize using an `initialScrollDone` flag as LenK has done since this method will get called more than once and if you call scrollToItemAtIndexPath: more than once it seems to render the collectionView unscrollable(iOS simulator iPhone 5/iOS 8) – maz Oct 20 '14 at 05:19
  • 1
    Works great in iOS8. Should be the accepted answer. – Nick Apr 18 '15 at 17:05
  • 6
    This didn't work for me in iOS 8.3. Had to call `[self.collectionView layoutIfNeeded];` before, which also works when you do this within `viewDidLoad`. – Brent Dillingham May 14 '15 at 22:23
  • 1
    This definitely should be the accepted answer. The answer above gave me a flash as the first cell was shown and then updated with my new cell, this method transitions seamlessly. – simon_smiley Jun 18 '15 at 11:43
  • this also works on iOS 9, and also does not mess up my auto layout. – Mr. Zystem Nov 26 '15 at 09:17
  • This caused a crash: attempted to scroll to invalid index path. I tried to access the first element. I'm on iOS 9. – NYC Tech Engineer Dec 07 '15 at 16:57
  • Nevermind, the crash was occurring because I was using indexPathWithIndex method to initialize an indexPath. – NYC Tech Engineer Dec 07 '15 at 18:51
  • Worked for me on iOS 9!! Thanks! – mattyb Aug 01 '16 at 23:36
  • Works on iOS 10. Amazing that something so simple should be so tricky. – Daniel Kanaan Sep 29 '16 at 00:09
17

I totally agree with the above answer. The only thing is that for me the solution was set the code in viewDidAppear

viewDidAppear 
{
    [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:YES];
}

When I used the viewWillAppear the scrolling didn't work.

pkamb
  • 26,648
  • 20
  • 124
  • 157
raistlin
  • 231
  • 2
  • 11
  • 2
    viewDidAppear has the disadvantage that you see the collection view moving/flashing, the view has already appeared (as the name implies) ... I really wonder why Apple has not designed this not so unusual case in a nicer way, the viewDidLayoutSubviews way is a bit awkward – TheEye Feb 19 '16 at 09:17
13

Swift:

your_CollectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: UICollectionViewScrollPosition.CenteredHorizontally, animated: true)

Swift 3

let indexPath = IndexPath(row: itemIndex, section: sectionIndex)

collectionView.scrollToItem(at: indexPath, at: UICollectionViewScrollPosition.right, animated: true)

Scroll Position:

UICollectionViewScrollPosition.CenteredHorizontally / UICollectionViewScrollPosition.CenteredVertically
minhazur
  • 4,670
  • 3
  • 23
  • 25
Rajesh Loganathan
  • 10,635
  • 4
  • 74
  • 88
12

this seemed to work for me, its similar to another answer but has some distinct differences:

- (void)viewDidLayoutSubviews
{     
   [self.collectionView layoutIfNeeded];
   NSArray *visibleItems = [self.collectionView indexPathsForVisibleItems];
   NSIndexPath *currentItem = [visibleItems objectAtIndex:0];
   NSIndexPath *nextItem = [NSIndexPath indexPathForItem:someInt inSection:currentItem.section];

   [self.collectionView scrollToItemAtIndexPath:nextItem atScrollPosition:UICollectionViewScrollPositionNone animated:YES];
}
greenhouse
  • 1,078
  • 13
  • 17
9

You can use GCD to dispatch the scroll into the next iteration of main run loop in viewDidLoad to achieve this behavior. The scroll will be performed before the collection view is showed on screen, so there will be no flashing.

- (void)viewDidLoad {
    dispatch_async (dispatch_get_main_queue (), ^{
        NSIndexPath *indexPath = YOUR_DESIRED_INDEXPATH;
        [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
    });
}
Peter Johnson
  • 3,663
  • 1
  • 20
  • 26
Tianyu Wang
  • 144
  • 1
  • 5
7

I would recommend doing it in collectionView: willDisplay: Then you can be sure that the collection view delegate delivers something. Here my example:

func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    /// this is done to set the index on start to 1 to have at index 0 the last image to have a infinity loop
    if !didScrollToSecondIndex {
        self.scrollToItem(at: IndexPath(row: 1, section: 0), at: .left, animated: false)
        didScrollToSecondIndex = true
    }
}
Eike
  • 1,951
  • 17
  • 31
  • 1
    I found that using didEndDisplaying, instead of willDisplay was the way to get this to work properly. willDisplay was okay as long as the cell was on the screen; however, if the cell wasn't on the screen yet, this method did not scroll me to it. didEndDisplaying though worked perfectly for that use case. Appreciate your answer setting me on the right path! – C6Silver Sep 14 '18 at 03:38
6

As an alternative to mentioned above. Call after data load:

Swift

collectionView.reloadData()
collectionView.layoutIfNeeded()
collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .right)
nCod3d
  • 567
  • 8
  • 11
1

All answers here - hacks. I've found better way to scroll collection view somewhere after relaodData:
Subclass collection view layout what ever layout you use, override method prepareLayout, after super call set what ever offset you need.
ex: https://stackoverflow.com/a/34192787/1400119

Community
  • 1
  • 1
trickster77777
  • 1,108
  • 2
  • 18
  • 30