39

I am trying to get a UITableview to go to the top of the page when I reload the table data when I call the following from

- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {

    // A bunch of code...

    [TableView reloadData];
}

Along those same lines, I would also like to be able to go to a specific section when I reload the table data.

I tried placing the following, which seems applicable only to the row, before and after the reload, but nothing happens:

[TableView scrollToRowAtIndexPath:0 atScrollPosition:UITableViewScrollPositionTop  animated:YES];
pkamb
  • 33,281
  • 23
  • 160
  • 191
AppsToKnow
  • 405
  • 1
  • 4
  • 4

14 Answers14

70

Here's another way you could do it:

Objective-C

[tableView setContentOffset:CGPointZero animated:YES];

Swift 3 and higher

tableView.setContentOffset(.zero, animated: true)

Probably the easiest and most straight forward way to do it.

Blazej SLEBODA
  • 8,936
  • 7
  • 53
  • 93
Erik B
  • 40,889
  • 25
  • 119
  • 135
  • Thanks. That go me to the top of the page, which was my immediate need. – AppsToKnow Apr 04 '11 at 20:47
  • 7
    Sometimes this should be executed after a delay like 0.1 seconds. e.g. `DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: { tableView.setContentOffset(.zero, animated: false) })` – Ahmed Khalaf Nov 13 '17 at 15:18
38

After reading all of the answers here and elsewhere, I finally found a solution that works reliably and does not show the scrolling after the data is loaded. My answer is based on this post and my observation that you need a negative offset if you are using content insets:

func reloadAndScrollToTop() {
    tableView.reloadData()
    tableView.layoutIfNeeded()
    tableView.contentOffset = CGPoint(x: 0, y: -tableView.contentInset.top)
}
Community
  • 1
  • 1
phatmann
  • 18,161
  • 7
  • 61
  • 51
  • 3
    This should be marked as correct answer because it honors any insets that were set before – Stefan Vasiljevic May 14 '17 at 04:54
  • A combination of this and changing `tableView.estimatedRowHeight` to the height of my top separator did the job for me. – Declan McKenna Oct 03 '17 at 13:05
  • `tableView.layoutIfNeeded()` helped me after 1-2 hours of struggling! – SoftDesigner Nov 06 '17 at 12:19
  • 2
    If you're going to use contentOffset you should run it before you reloadData as reloadData runs asynchronously, therefore reloadData probably won't complete before contentOffset completes so your offset won't be accurate – Braden Holt Feb 18 '19 at 20:24
  • 1
    Tried this with several adjustments. Best I got was a result an offset of 0, -25 after setting it to zero. Curious. But the solution with scrollToRow works: `tableView.scrollToRow(at: IndexPath.init(row: 0, section: 0), at: .top, animated: true)` - with checking for row and section count > 0. – corban Mar 18 '19 at 18:08
  • You saved my time. Thank you so much. This should be mark correct answer. – Jenny Tran Apr 23 '19 at 04:45
24

To scroll to a specific section you need to specify the index path to it:

NSIndexPath *topPath = [NSIndexPath indexPathForRow:0 inSection:0];
[TableView scrollToRowAtIndexPath:topPath
                 atScrollPosition:UITableViewScrollPositionTop
                         animated:YES];

If you are uncertain about the number of rows in your tableView. Use the following:

NSIndexPath *topPath = [NSIndexPath indexPathForRow:NSNotFound inSection:0];
[TableView scrollToRowAtIndexPath:topPath
                 atScrollPosition:UITableViewScrollPositionTop
                         animated:YES];
Rajan Balana
  • 3,775
  • 25
  • 42
ragamufin
  • 4,113
  • 30
  • 32
23

This is working for me.

tableView.scrollToRow(at: IndexPath.init(row: 0, section: 0), at: .top, animated: true)
Vetuka
  • 1,523
  • 1
  • 24
  • 40
23

WITHOUT ANIMATION

I'm on iOS11, using a plain tableview style with sticky headers and somehow the only way I got this to work correctly to have the tableview really on top after a reload without any strange flickers/animation behaviours was to use these methods in this order where ALL of these are necessary:

[self.tableView setContentOffset:CGPointZero animated:NO];
[self.tableView reloadData];
[self.tableView layoutIfNeeded];
[self.tableView setContentOffset:CGPointZero animated:NO];

I'm serious that all of these are necessary, even though it seems the first line is not relevant at all. Oh yeah, also very important, do NOT use this:

self.tableView.contentOffset = CGPointZero;

You would think that it's the same as the "setContentOffset animated:FALSE" but apparently it's not! Apple treats this method differently and in my case this only worked when using the full method with animated:FALSE.

WITH ANIMATION

I also wanted to try this with a nice scrolling animation to the top. There the content offset methods seemed to still cause some strange animation behaviours. The only way I got this working with a nice animation after some trial and error were these methods in this exact order:

[self.tableView reloadData];
[self.tableView layoutIfNeeded];
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:TRUE];

Warning, make sure you have at least 1 row before running the last line of code to avoid crashing :)

I hope this helps someone someday!

Bob de Graaf
  • 2,630
  • 1
  • 25
  • 43
  • Works great with parallex header table view. Thank you! – mmk Aug 24 '19 at 12:30
  • Work well with dynamic cell – Jadian Sep 26 '19 at 02:13
  • I had a screen X with a tableView and another screen Y with a list of all X's users can choose from. I wanted to choose a new row from the Y list so the my tableView on screen X would reload its data and scroll to the top of the header of the tableView on screen X. Only calling setContentOffset animated code before and after the reloadData worked. – tiw Sep 07 '20 at 12:58
  • When I used scrollToRowAtIndexPath, it didn't scroll to all the way to show the tableView's header – tiw Sep 07 '20 at 13:08
4

UITableView is a subclass of UIScrollView, so you can also use:

[mainTableView scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES];
D.Enchev
  • 116
  • 1
  • 5
3

I was a bit confused by some of the answers I found to this question due to incomplete explanations. From what I've gathered, there's two routes to accomplish what OP requested and they are as follows:

  1. If animation isn't a requirement, I'd suggest reseting scroll position before data loads using one of the offset methods. The reason you should run offset before reloadData is that reloadData runs asynchronously. If you run offset after reload, your scroll position will probably be a few rows down from the top.

    tableView.setContentOffset(.zero, animated: false)
    tableView.reloadData()
    
  2. If animation is a requirement, position your scroll to the top row after you run reloadData using scrollToRow. The reason scrollToRow works here is that it positions you at the top of the table, versus the offset method that positions you relative to data that has already loaded.

    tableView.reloadData()
    tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
    
pkamb
  • 33,281
  • 23
  • 160
  • 191
Braden Holt
  • 1,544
  • 1
  • 18
  • 32
2

In swift 3.1

DispatchQueue.main.async(execute: {

        self.TableView.reloadData()
        self.TableView.contentOffset = .zero

    })
Mahipalsinh
  • 123
  • 7
2

For those still finding solution to this, you can use the following extension of UITableView.

Including a check where first section has row(s) or not. It goes till the section with row(s) comes and then scrolls to that one.

extension UITableView {

    func scrollToFirst() {

        self.reloadData() // if you don't want to reload data, remove this one.
        for i in 0..<self.numberOfSections {

            if self.numberOfRows(inSection: i) != 0 {

                self.scrollToRow(at: IndexPath(row: 0, section: i), at: .top, animated: true) 
                break 
            }
        }
    }
}

Hope that helped you.

viral
  • 4,168
  • 5
  • 43
  • 68
1

If you scroll before reloading and the number of rows decreases, you can have some strange animating behavior. To ensure the scrolling happens after reload, use a method like this:

- (void)reloadTableViewAndScrollToTop:(BOOL)scrollToTop {
    [self.tableView reloadData];
    if (scrollToTop) {
        [self.tableView setContentOffset:CGPointZero animated:YES];
    }
}
skensell
  • 1,421
  • 12
  • 21
1

You must scroll UITableView at the first row of your table view by using this line of code:

self.yourTableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
Paul Roub
  • 36,322
  • 27
  • 84
  • 93
Rahul Panzade
  • 1,302
  • 15
  • 12
0

After considering the options discussed above my version of the solutions is the following. It works consistently in iOS 11 & 12 for both UICollectionView & UITableView.

UICollectionView:

DispatchQueue.main.async { [weak self] in
    guard self?.<your model object>.first != nil else { return }

    self?.collectionView.scrollToItem(at: IndexPath(row: 0, section: 0),
                               at: .top,
                                     animated: false)
}

UITableView:

DispatchQueue.main.async { [weak self] in
    guard self?.<your model object>.first != nil else { return }

    self?.collectionView.scrollToRow(at: IndexPath(row: 0, section: 0),
                               at: .top,
                                     animated: false)
}
DCDC
  • 486
  • 5
  • 9
0

Perfectly to the top of page on reload, by twice reloading

dataForSource = nil
tableView.reloadData()

DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
    self.dataForSource = realData
    self.tableView.reloadData()
}
black_pearl
  • 2,549
  • 1
  • 23
  • 36
0
Xcode 13.2.1 & Swift 5.6

tableView.reloadData()
tableView.layoutIfNeeded()
tableView.contentOffset = .zero