408
votes

In my table view I have to scroll to the top. But I cannot guarantee that the first object is going to be section 0, row 0. May be that my table view will start from section number 5.

So I get an exception, when I call:

[mainTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:NO];

Is there another way to scroll to the top of table view?

30

30 Answers

886
votes

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

[mainTableView scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES];

Or

[mainTableView setContentOffset:CGPointZero animated:YES];

And in Swift:

mainTableView.setContentOffset(CGPointZero, animated:true)

And in Swift 3 & above:

mainTableView.setContentOffset(.zero, animated: true)
247
votes

Note: This answer isn't valid for iOS 11 and later.

I prefer

[mainTableView setContentOffset:CGPointZero animated:YES];

If you have a top inset on your table view, you have to subtract it:

[mainTableView setContentOffset:CGPointMake(0.0f, -mainTableView.contentInset.top) animated:YES];
87
votes

Possible Actions:

1

func scrollToFirstRow() {
    let indexPath = NSIndexPath(forRow: 0, inSection: 0)
    self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Top, animated: true)
}

2

func scrollToLastRow() {
    let indexPath = NSIndexPath(forRow: objects.count - 1, inSection: 0)
    self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: true)
}

3

func scrollToSelectedRow() {
    let selectedRows = self.tableView.indexPathsForSelectedRows
    if let selectedRow = selectedRows?[0] as? NSIndexPath {
        self.tableView.scrollToRowAtIndexPath(selectedRow, atScrollPosition: .Middle, animated: true)
    }
}

4

func scrollToHeader() {
    self.tableView.scrollRectToVisible(CGRect(x: 0, y: 0, width: 1, height: 1), animated: true)
}

5

func scrollToTop(){
    self.tableView.setContentOffset(CGPointMake(0,  UIApplication.sharedApplication().statusBarFrame.height ), animated: true)
}

Disable Scroll To Top:

func disableScrollsToTopPropertyOnAllSubviewsOf(view: UIView) {
    for subview in view.subviews {
        if let scrollView = subview as? UIScrollView {
            (scrollView as UIScrollView).scrollsToTop = false
        }
        self.disableScrollsToTopPropertyOnAllSubviewsOf(subview as UIView)
    }
}

Modify and use it as per requirement.

Swift 4

  func scrollToFirstRow() {
    let indexPath = IndexPath(row: 0, section: 0)
    self.tableView.scrollToRow(at: indexPath, at: .top, animated: true)
  }
27
votes

It's better to not use NSIndexPath (empty table), nor assume that top point is CGPointZero (content insets), that's what I use -

[tableView setContentOffset:CGPointMake(0.0f, -tableView.contentInset.top) animated:YES];

Hope this helps.

21
votes

Swift 4:

This works very well:

//self.tableView.reloadData() if you want to use this line remember to put it before 
let indexPath = IndexPath(row: 0, section: 0)
self.tableView.scrollToRow(at: indexPath, at: .top, animated: true)
20
votes

I've encountered an issue calling trying some of the methods on an empty tableView. Here's another option for Swift 4 that handles empty tableviews.

extension UITableView {
  func hasRowAtIndexPath(indexPath: IndexPath) -> Bool {
    return indexPath.section < self.numberOfSections && indexPath.row < self.numberOfRows(inSection: indexPath.section)
  }

  func scrollToTop(animated: Bool) {
    let indexPath = IndexPath(row: 0, section: 0)
    if self.hasRowAtIndexPath(indexPath: indexPath) {
      self.scrollToRow(at: indexPath, at: .top, animated: animated)
    }
  }
}

Usage:

// from yourViewController or yourTableViewController
tableView.scrollToTop(animated: true)//or false
19
votes

On iOS 11, use adjustedContentInset to correctly scroll to top for both cases when the in-call status bar is visible or not.

if (@available(iOS 11.0, *)) {
    [tableView setContentOffset:CGPointMake(0, -tableView.adjustedContentInset.top) animated:YES];
} else {
    [tableView setContentOffset:CGPointMake(0, -tableView.contentInset.top) animated:YES];
}

Swift:

if #available(iOS 11.0, *) {
    tableView.setContentOffset(CGPoint(x: 0, y: -tableView.adjustedContentInset.top), animated: true)
} else {
    tableView.setContentOffset(CGPoint(x: 0, y: -tableView.contentInset.top), animated: true)
}
17
votes

DONT USE

 tableView.setContentOffset(.zero, animated: true)

It can sometimes set the offset improperly. For example, in my case, the cell was actually slightly above the view with safe area insets. Not good.

INSTEAD USE

 tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
12
votes

For tables that have a contentInset, setting the content offset to CGPointZero will not work. It'll scroll to the content top vs. scrolling to the table top.

Taking content inset into account produces this instead:

[tableView setContentOffset:CGPointMake(0, -tableView.contentInset.top) animated:NO];
10
votes

This code let's you scroll a specific section to top

CGRect cellRect = [tableinstance rectForSection:section];
CGPoint origin = [tableinstacne convertPoint:cellRect.origin 
                                    fromView:<tableistance>];
[tableinstance setContentOffset:CGPointMake(0, origin.y)];
8
votes

Swift:

tableView.setContentOffset(CGPointZero, animated: true)
8
votes

Swift 3

tableView.setContentOffset(CGPoint.zero, animated: true)

if tableView.setContentOffset don't work.

Use:

tableView.beginUpdates()
tableView.setContentOffset(CGPoint.zero, animated: true)
tableView.endUpdates()
8
votes

Swift 5, iOS 13

I know this question already has a lot of answers but from my experience this method always works:

let last = IndexPath(row: someArray.count - 1, section: 0)
tableView.scrollToRow(at: last, at: .bottom, animated: true)

And this is especially true if you're working with animations (like keyboard) or certain async tasks—the other answers will often scroll to the almost bottom. If for some reason this doesn't get you all the way to the bottom, it's almost certainly because of a competing animation so the workaround is to dispatch this animation to the end of the main queue:

DispatchQueue.main.async {
    let last = IndexPath(row: self.someArray.count - 1, section: 0)
    self.tableView.scrollToRow(at: last, at: .bottom, animated: true)
}

This may seem redundant since you're already on the main queue but it's not because it serializes the animations.

7
votes

Since my tableView is full of all kinds of insets, this was the only thing that worked well:

Swift 3

if tableView.numberOfSections > 0 && tableView.numberOfRows(inSection: 0) > 0 {
  tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
}

Swift 2

if tableView.numberOfSections > 0 && tableView.numberOfRowsInSection(0) > 0 {
  tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 0), atScrollPosition: .Top, animated: true)
}
7
votes

Adding on to what's already been said, you can create a extension (Swift) or category (Objective C) to make this easier in the future:

Swift:

extension UITableView {
    func scrollToTop(animated: Bool) {
        setContentOffset(CGPointZero, animated: animated)
    }
}

Any time you want to scroll any given tableView to the top you can call the following code:

tableView.scrollToTop(animated: true)
6
votes

I prefer the following, as it takes into account an inset. If there is no inset, it will still scroll to the top as the inset will be 0.

tableView.setContentOffset(CGPoint(x: 0, y: -tableView.contentInset.top), animated: true)
5
votes

Swift :

if you don't have tableView header :

tableView.setContentOffset(CGPointMake(0,  UIApplication.sharedApplication().statusBarFrame.height ), animated: true)

if so :

tableView.setContentOffset(CGPointMake(0, -tableViewheader.frame.height   + UIApplication.sharedApplication().statusBarFrame.height ), animated: true)
4
votes

Here's what I use to work correctly on iOS 11:

extension UIScrollView {
    func scrollToTop(animated: Bool) {
        var offset = contentOffset
        if #available(iOS 11, *) {
            offset.y = -adjustedContentInset.top
        } else {
            offset.y = -contentInset.top
        }
        setContentOffset(offset, animated: animated)
    }
}
4
votes

In Swift 5 , Thanks @Adrian's answer a lot

extension UITableView{

    func hasRowAtIndexPath(indexPath: IndexPath) -> Bool {
        return indexPath.section < numberOfSections && indexPath.row < numberOfRows(inSection: indexPath.section)
    }

    func scrollToTop(_ animated: Bool = false) {
        let indexPath = IndexPath(row: 0, section: 0)
        if hasRowAtIndexPath(indexPath: indexPath) {
            scrollToRow(at: indexPath, at: .top, animated: animated)
        }
    }

}

Usage:

tableView.scrollToTop()
3
votes

using contentOffset is not the right way. this would be better as it is table view's natural way

tableView.scrollToRow(at: NSIndexPath.init(row: 0, section: 0) as IndexPath, at: .top, animated: true)
3
votes

This was the only code snippet that worked for me

Swift 4:

    tableView.scrollRectToVisible(CGRect(x: 0, y: 0, width: 1, height: 1), animated: true)
    tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
    tableView.setContentOffset(CGPoint(x: 0, y: -70), animated: true)

P.S. 70 is the height of my header and table view cell

2
votes
func scrollToTop() {
        NSIndexPath *topItem = [NSIndexPath indexPathForItem:0 inSection:0];
        [tableView scrollToRowAtIndexPath:topItem atScrollPosition:UITableViewScrollPositionTop animated:YES];
}

call this function wherever you want UITableView scroll to top

2
votes

Swift 4 via extension, handles empty table view:

extension UITableView {
    func scrollToTop(animated: Bool) {
        self.setContentOffset(CGPoint.zero, animated: animated);
    }
}
2
votes

I use tabBarController and i have a few section in my tableview at every tab, so this is best solution for me.

extension UITableView {

    func scrollToTop(){

        for index in 0...numberOfSections - 1 {
            if numberOfSections > 0 && numberOfRows(inSection: index) > 0 {
                scrollToRow(at: IndexPath(row: 0, section: index), at: .top, animated: true)
                break
            }

            if index == numberOfSections - 1 {
                setContentOffset(.zero, animated: true)
                break
            }
        }

    }

}
1
votes

I had to add the multiply by -1 * to the sum of the status bar and the navigation bar, because it was going that height off the screen,

self.tableView.setContentOffset(CGPointMake(0 , -1 * 
  (self.navigationController!.navigationBar.height +  
  UIApplication.sharedApplication().statusBarFrame.height) ), animated:true)
1
votes

in swift

your row = selectioncellRowNumber your section if you have = selectionNumber if you dont have set is to zero

//UITableViewScrollPosition.Middle or Bottom or Top

var lastIndex = NSIndexPath(forRow:  selectioncellRowNumber, inSection: selectionNumber)
self.tableView.scrollToRowAtIndexPath(lastIndex, atScrollPosition: UITableViewScrollPosition.Middle, animated: true)
1
votes

Here Is The Code To ScrollTableView To Top Programatically

Swift:

self.TableView.setContentOffset(CGPointMake(0, 1), animated:true)
1
votes

In Swift-3 :

self.tableView.setContentOffset(CGPoint.zero, animated: true)
1
votes

Here is a simple example of UIScrollView extension that you can use everywhere in your app.

1) First you should create enum with possible scroll directions:

enum ScrollDirection {
    case top, right, bottom, left

    func contentOffsetWith(_ scrollView: UIScrollView) -> CGPoint {
        var contentOffset = CGPoint.zero
        switch self {
            case .top:
                contentOffset = CGPoint(x: 0, y: -scrollView.contentInset.top)
            case .right:
                contentOffset = CGPoint(x: scrollView.contentSize.width - scrollView.bounds.size.width, y: 0)
            case .bottom:
                contentOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.bounds.size.height)
            case .left:
                contentOffset = CGPoint(x: -scrollView.contentInset.left, y: 0)
            }
        return contentOffset
    }
}

2) Then add extension to UIScrollView:

extension UIScrollView {
    func scrollTo(direction: ScrollDirection, animated: Bool = true) {
        self.setContentOffset(direction.contentOffsetWith(self), animated: animated)
    }
}

3) Thats it! Now you can use it:

myScrollView.scrollTo(.top, animated: false)

This scroll bind to the tableView's content size and it looks more natural than scroll to CGPoint.zero

0
votes

If you i would like move scroll animation in the table, use this code. The scroll move to top with animation in .5 seconds.

[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];

[_tableContent scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES];

[UIView commitAnimations];