61

I have a UITableView with cells that are dynamically updated. Everything works fine apart from when tableview.reload is called (see below) to refresh the cells in the table I would like the table to scroll to the bottom to show the new entries.

- (void)reloadTable:(NSNotification *)notification {
    NSLog(@"RELOAD TABLE ...");
    [customTableView reloadData];
    // Scroll to bottom of UITable here ....
}

I was planning to use scrollToRowAtIndexPath:atScrollPosition:animated: but then noticed that I don't have access to an indexPath.

Does anyone know how to do this, or of a delegate callback that I could use?

User_1191
  • 981
  • 2
  • 8
  • 24
fuzzygoat
  • 26,573
  • 48
  • 165
  • 294
  • 2
    Similar to [link](http://stackoverflow.com/questions/2156614/how-to-start-uitableview-on-the-last-cell). Apparently, the trick there was to call `numberOfRowsInSection:`. – Andrei Stanescu Feb 25 '11 at 01:07

12 Answers12

123

Use:

NSIndexPath* ipath = [NSIndexPath indexPathForRow: cells_count-1 inSection: sections_count-1];
[tableView scrollToRowAtIndexPath: ipath atScrollPosition: UITableViewScrollPositionTop animated: YES];

Or you can specify the section index manually (If one section => index=0).

Dilip Manek
  • 9,095
  • 5
  • 44
  • 56
Max
  • 16,679
  • 4
  • 44
  • 57
41

Another solution is to flip the table vertically, and flip each cell vertically:

Apply the transform to the UITableView when initializing:

tableview.transform = CGAffineTransformMakeScale(1, -1);

and in cellForRowAtIndexPath:

cell.transform = CGAffineTransformMakeScale(1, -1);

This way you don't need workarounds for scrolling issues, but you will need to think a little harder about contentInsets/contentOffsets and header/footer interactions.

Michael Wilson
  • 851
  • 6
  • 13
  • 1
    This is hacky, but its so much more visually appealing than the other solutions! – David Wong Sep 10 '15 at 06:49
  • You are genius! best way to do this. So elegant. – Faruk Dec 21 '15 at 22:21
  • I hate to say it but this hack works better then all other methods. – SpaceTrucker Apr 13 '16 at 23:17
  • 4
    SlackViewController uses this technique :) – aryaxt Apr 19 '16 at 18:21
  • @michael-wilson please please please tell us why this even works. – George Maisuradze Jan 08 '17 at 13:44
  • 4
    This is indeed a brilliant solution! It's not that difficult to see why this works. @GeorgeMaisuradze the first transform flips the whole tableview up-side down. So all cells are actually unreadable since it's up-side down. But without the cell transform you can already see that since the tableview is upside-down any pull-to-refresh would be at the bottom and you can use scrollToTop to easily scroll to the bottom: [self.tableView setContentOffset:CGPointZero animated:animated]; Then simply use the flip-transform on the cells again to make them readable. Brilliant! – Bob de Graaf Mar 24 '17 at 08:55
  • Awesome . Just FYI. You'll need to swap your header with footer and flip the views too ;) – Sourav Chandra Apr 27 '17 at 05:16
  • Unfortunately, footer is showing on the top :(.which is basically a big empty space. – coolly Nov 08 '17 at 16:33
  • Wow, I spent 2 days trying to create smooth scrolling and loading previous data when the scroll approaches top. Finally found this post and this solution totally changed my approach. Now everything works so smooth that I would give 100 pluses to you Mr. Michael Wilson. Thanks! – Art Feb 04 '18 at 19:39
34
-(void)scrollToBottom{

        [self.tableView scrollRectToVisible:CGRectMake(0, self.tableView.contentSize.height - self.tableView.bounds.size.height, self.tableView.bounds.size.width, self.tableView.bounds.size.height) animated:YES];

}
mkral
  • 4,065
  • 4
  • 28
  • 53
  • 1
    This seems to be a better solution for a general case of scrolling to the bottom. If the contents of your tableview change, or you've deallocated the table when you attempt to scroll, cells_count will probably be 0 and you may run into some issues. Scrolling to the end of the table by it's size may be slightly more robust. I'll be using this moving forward. Admittedly, the question doesn't require this. – Roderic Campbell Apr 15 '14 at 22:23
  • Great job boss... Thanks! – Anand Gautam Sep 05 '14 at 15:30
10
//In swift 

var iPath = NSIndexPath(forRow: self.tableView.numberOfRowsInSection(0)-1, 
                        inSection: self.tableView.numberOfSections()-1)
self.tableView.scrollToRowAtIndexPath(iPath, 
                                      atScrollPosition: UITableViewScrollPosition.Bottom,                     
                                      animated: true)
ytbryan
  • 2,644
  • 31
  • 49
8

Swift 3

For all the folks here trying to figure out how to solve this problem the key is to call the .layoutIfNeeded() method after .reloadData() :

tableView.reloadData()
tableView.layoutIfNeeded()
tableView.setContentOffset(CGPoint(x: 0, y: tableView.contentSize.height - tableView.frame.height), animated: false)

I was working with multiple sections in UITableView and it worked well.

Chris Tsitsaris
  • 590
  • 10
  • 12
  • 1
    Thank you. It is the most valuable answer for the case when autolayout is used. There are plenty of possibilities to perform scroll and calculate offset, but they are of no help because layout is not updated yet, when setContentOffset called. And call .layoutIfNeeded() resolves this problem. – Vladimir Aug 11 '20 at 15:38
  • This actually works if number of rows changes :) thanks – Matej Ukmar Feb 02 '21 at 18:48
8

Fot Swift 5

extension UITableView {
    func scrollToBottom(animated: Bool = true) {
        let section = self.numberOfSections
        if section > 0 {
            let row = self.numberOfRows(inSection: section - 1)
            if row > 0 {

                self.scrollToRow(at: IndexPath(row: row-1, section: section-1), at: .bottom, animated: animated)
            }
        }
    }
}
pVaskou
  • 163
  • 1
  • 9
6

As this is something you might want to use really often, I suggest that you create a class extension on UITableView :

extension UITableView {
    func scrollToBottom(animated: Bool = true) {
        let section = self.numberOfSections
        if section > 0 {
            let row = self.numberOfRowsInSection(section - 1)
            if row > 0 {
                self.scrollToRowAtIndexPath(NSIndexPath(forRow: row - 1, inSection: section - 1), atScrollPosition: .Bottom, animated: animated)
            }
        }
    }
}
jfgrang
  • 1,148
  • 13
  • 13
5

This is another solution, worked well in my case, when cell height is big.

- (void)scrollToBottom
{
    CGPoint bottomOffset = CGPointMake(0, _bubbleTable.contentSize.height - _bubbleTable.bounds.size.height);
    if ( bottomOffset.y > 0 ) {
        [_bubbleTable setContentOffset:bottomOffset animated:YES];
    }
}
Denis Kutlubaev
  • 15,320
  • 6
  • 84
  • 70
1

extension is better to be done on UIScrollView instead of UITableView, this way it works on scrollView, tableView, collectionView (vertical), UIWebView (inner scroll view), etc

public extension UIScrollView {

    public func scrollToBottom(animated animated: Bool) {
        let rect = CGRectMake(0, contentSize.height - bounds.size.height, bounds.size.width, bounds.size.height)
        scrollRectToVisible(rect, animated: animated)
    }

}
aryaxt
  • 76,198
  • 92
  • 293
  • 442
1

Swift 5

    func scrollToBottom() {
        let section = self.tableView.numberOfSections
        let row = self.tableView.numberOfRows(inSection: self.tableView.numberOfSections - 1) - 1;
        guard (section > 0) && (row > 0) else{ // check bounds
            return
        }
        let indexPath = IndexPath(row: row-1, section: section-1)
        self.tableView.scrollToRow(at: indexPath, at: .top, animated: true)
    }

I dont agree that we should user cells_count,sections_count,self.dateSource.countand so on, Instead, the delegate will be better.

mistdon
  • 1,773
  • 16
  • 14
0

This is the best way.

- (void)scrollToBottom
{
    CGFloat yOffset = 0;

    if (self.tableView.contentSize.height > self.tableView.bounds.size.height) {
        yOffset = self.tableView.contentSize.height - self.tableView.bounds.size.height;
    }

    [self.tableView setContentOffset:CGPointMake(0, yOffset) animated:NO];
}
Dhiru
  • 3,040
  • 3
  • 25
  • 69
-1

try this code, It may help you:

    self.tableView.reloadData()
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+0.1, execute: {
        let indexPath = IndexPath(row: self.dateSource.count-1, section: 0)
        self.tableView.scrollToRow(at: indexPath, at: UITableViewScrollPosition.bottom, animated: true)
    })
muhlenXi
  • 39
  • 6