55

I have a horizontal UICollectionView which works fine and scrolls. When I tap an item I update my data and call reloadData. This works and the new data is displayed in the UICollectionView. The problem is the scroll position doesn't change and it is still viewing the last place. I want to reset the scrolling to the top (Or left in my case). How can I do this?

Greg Parker
  • 7,972
  • 2
  • 24
  • 21
MulletDevil
  • 919
  • 2
  • 8
  • 16
  • Can you show your code when having this problem? I am trying to get my scrollview to not scroll when I reload data lol i'm having the opposite of your problem! – Wayne Filkins Oct 12 '16 at 06:16

9 Answers9

174

You want setContentOffset:. It takes a CGPoint as and argument that you can set to what ever you want using CGPointMake, but if you wish to return to the very beginning of the collection, you can simply use CGPointZero.

[collectionView setContentOffset:CGPointZero animated:YES];
Mick MacCallum
  • 129,200
  • 40
  • 280
  • 281
  • if the collectionview enable refresh view ,it will show refresh view – jasonhao Jul 12 '13 at 03:26
  • 1
    +1 `setContentOffset` helped me to work this for UIScrollView as well. – NeverHopeless Aug 28 '13 at 07:05
  • 32
    In the event that you are using the `contentInsets`, you can use the following point for your content offset `CGPointMake(-collectionView.contentInset.left, -collectionView.contentInset.top)` – TPoschel Jan 06 '14 at 19:45
  • 2
    The accepted answer (scrollToItemAtIndexPath) for me resets scroll to the first row, which places the header item (viewForSupplementaryElementOfKind) out of view. setContentOffset to CGPointZero places the scroll at the complete top, including any header cells. – Scott Heaberlin Feb 11 '14 at 05:08
  • 2
    It also has the advantage that one doesen't need to verify if the index path exists (e.g.: empty collection view) – Adrian Jul 01 '15 at 12:31
34

You can use this method to scroll to any item you want:

- (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath 
               atScrollPosition:(UICollectionViewScrollPosition)scrollPosition 
                       animated:(BOOL)animated
Alex Cio
  • 6,014
  • 5
  • 44
  • 74
Nils Hott
  • 1,650
  • 15
  • 16
  • 1
    Also doesn't allow for an offset – Dermot Jul 30 '15 at 02:34
  • Forget it, comment below solves it. [collectionView setContentOffset:CGPointZero animated:YES]; – inigo333 Aug 20 '15 at 15:56
  • 4
    A side note, but if you set animated:NO and wrap this in an animation block (such as UIView withSpring...) you can have a custom animation style/speed for the scrolling. – Dermot Aug 21 '15 at 06:06
  • Note that this implementation will also invoke scroll delegate methods, which is exactly what I wanted. Thanks. – ClockWise Jan 08 '19 at 12:03
  • 1
    This solution is more suitable for scrolling to a specific item. "setContentOffset" is better & safer for simply scrolling to the top. This solution requires some check to avoid runtime exceptions. – Abdurrahman Mubeen Ali Jan 14 '19 at 15:49
  • **WARNING:** This can cause a crash on iOS 12 and earlier if content is still loading. I recommend using the method with setContentOffset instead. – Alain Stulz Mar 23 '20 at 11:08
23

I use this quite often in different parts of my app so I just extended UIScrollView so it can be used on any scroll view and scroll view subclass:

extension UIScrollView {        
    /// Sets content offset to the top.
    func resetScrollPositionToTop() {
        self.contentOffset = CGPoint(x: -contentInset.left, y: -contentInset.top)
    }
}

So whenever I need to reset the position:

self.collectionView.resetScrollPositionToTop()
Daniel Galasko
  • 23,617
  • 8
  • 77
  • 97
13

In Swift:

collectionView.setContentOffset(CGPointZero, animated: true)

If you leave the view that houses the collection view, make a change in the 2nd view controller, and need the collection view to update upon returning:

@IBAction func unwindTo_CollectionViewVC(segue: UIStoryboardSegue) {
    //Automatic table reload upon unwind
    viewDidLoad()
    collectionView.reloadData()
    collectionView.setContentOffset(CGPointZero, animated: true)
}

In my situation the unwind is great because the viewDidLoad call will update the CoreData context, the reloadData call will make sure the collectionView updates to reflect the new CoreData context, and then the contentOffset will make sure the table sets back to the top.

Dave G
  • 12,042
  • 7
  • 57
  • 83
7

If you view controller contains safe area, code:

collectionView.setContentOffset(CGPointZero, animated: true)

doesn't work, so you can use universal extension:

extension UIScrollView {
    func scrollToTop(_ animated: Bool) {
        var topContentOffset: CGPoint
        if #available(iOS 11.0, *) {
            topContentOffset = CGPoint(x: -safeAreaInsets.left, y: -safeAreaInsets.top)
        } else {
            topContentOffset = CGPoint(x: -contentInset.left, y: -contentInset.top)
        }
        setContentOffset(topContentOffset, animated: animated)
    }
}
Alexander
  • 157
  • 2
  • 6
  • Code-only answers are discouraged. Please click on edit and add a paragraph or two summarising how your code addresses the question, or perhaps explain how your answer differs from the previous answers. Thanks – Nick Nov 28 '18 at 05:56
  • This is the only answer that takes into account safeAreaInsets. All the others answers scrolled to the incorrect position for my collectionView. – Steven Baughman Dec 09 '18 at 03:05
6

CGPointZero in my case shifted the content of the collection view because you are not taking in count the content inset. This is what it worked for me:

    CGPoint topOffest = CGPointMake(0,-self.collectionView.contentInset.top);
    [self.collectionView setContentOffset:topOffest animated:YES];
4

Sorry I couldn't comment at the time of this writing, but I was using a UINavigationController and CGPointZero itself didn't get me to the very top so I had to use the following instead.

CGFloat compensateHeight = -(self.navigationController.navigationBar.bounds.size.height+[UIApplication sharedApplication].statusBarFrame.size.height);
[self.collectionView setContentOffset:CGPointMake(0, compensateHeight) animated:YES];

Hope this helps somebody in future. Cheers!

Ryan
  • 490
  • 5
  • 14
2

To deal with an UINavigationController and a transparent navigation bar, I had to calculate the extra top offset to perfectly match the position of the first element.

Swift 1.1 code:

     let topOffest = CGPointMake(0, -(self.collectionView?.contentInset.top ?? O))
     self.collectionView?.setContentOffset(topOffest, animated: true)

Cheers!

Kevin Delord
  • 2,498
  • 24
  • 22
0
  • SWIFT 4.2 SOLUTION- Say I have a tableView with each tableViewCell containing a HorizontalCollectionView Due to reuse of cells, even though first tableViewCell is scrolled to say page 4, downwards, other cell is also set to page 4.

This works in such cases- In the cellForRowAt func in tableView, let cell = //declaring a cell using deque cell.myCollectionView.contentOffset = CGPoint(x:0,y:0)

fin
  • 77
  • 1
  • 3