7

How can I scroll my table view to the bottom when using dynamic cell height?

For some reason this code does not work in this scenario:

[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];

Thanks!

EDIT: Use @Hasiya's code to scroll to the bottom, for some of you that alone might do the trick.

Balázs Vincze
  • 3,550
  • 5
  • 29
  • 60

5 Answers5

9

The following worked for my Swift 3.1 code using Xcode 8.3.2 targeting iOS 10.1 for iPhone only.

I had the same problem as the OP. My app included a messaging function. I wanted the last post to be visible when the message view was pushed onto the stack. My tableview cell heights are variable due to the content of the messages. None of the solutions worked to my satisfaction (the last tableview row was not visible or only partially visible). But, here is what worked for me:

  • In the Interface Builder (IB) set the cell Row Height to a value greater than I expected my average row height to be in my case 50 (make sure to check the 'Custom' checkbox).
  • In the ViewController where the tableview is implemented inside the 'viewDidLoad' function set the tableview's 'rowHeight' property as follows:

    myTableView.rowHeight = UITableViewAutomaticDimension
    
  • Also in the 'viewDidLoad' function set the tableview's 'estimatedRowHeight' to a value higher than you expect your average row height to be. In my case 140.

Finally and most importantly I wrote this bit of code:

extension UITableView {

    func scrollToBottom() {
        let rows = self.numberOfRows(inSection: 0)

        let indexPath = IndexPath(row: rows - 1, section: 0)
        self.scrollToRow(at: indexPath, at: .top, animated: true)
    }
}

Notice the at: .top — this is what solved my problem. Passing the .bottom value for the UITableViewScrollPosition parameter just didn't work. Even passing .middle worked — but not .bottom.

So, any time my dataset need to be updated or a new message arrives I call

func getTableViewData(){
    // Get your data here any way you usually do..

    // call to reload
    myTableView.reloadData()
    // call the scroll function
    myTableView.scrollToBottom()
}

I tested the results of this 'method' by simply changing the at: .bottom parameter to at: .top. Every time I changed it to at: .top the last table rows were fully visible and the table scrolled all the way to the bottom.

Barns
  • 4,850
  • 3
  • 17
  • 31
  • Indeed – `.top` works for scrolling to bottom much better than `.bottom`. Amazing discovery! – villapossu Aug 08 '17 at 12:56
  • Amazing find! I have been searching for a working solution to this problem for weeks and `.top` works flawlessly! – Jeff Jones Aug 31 '17 at 01:36
  • How good is this performance-wise? All attempts with `scrollToRow` so far for variable height rows have been too bad. I'm talking rows coming in many and randomly in a fast pace. – Jonny Sep 14 '17 at 09:59
  • I have no performance issues. My `TableView` is fairly simple. Images are loading on background thread if I have them. – Barns Sep 14 '17 at 17:51
4

Eventually, I have found the answer. The reason why scrolling to the bottom does not work (and inserting/deleting rows are buggy as well, as I found out later), is because cell height is not properly estimated. To get around this, try to return close estimations in estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath. However, if you can't do that (and in most cases you won't be able to) there is still a great solution: Store the heights of the cells in a dictionary, and return those for the estimatedHeight when cells are reused (e.g. table view is scrolled). I have found this solution in another question, so please go and check that out for the actual code on how you would carry this out. (although probably, many of you can write this for themselves)

Original solution by @dosdos

EDIT: Maybe to fix just scrolling to the bottom this is unnecessary, however highly recommended in my opinion. If you don't use this for estimating your row height, you will encounter other problems such as buggy scrolling and worse performance at a large number of cells.

Also you need to add this to viewDidAppear

let lastItem = IndexPath(item: dataSource.count - 1, section: 0)
tableView.scrollToRow(at: lastItem, at: .bottom, animated: true)
Community
  • 1
  • 1
Balázs Vincze
  • 3,550
  • 5
  • 29
  • 60
1

Hope this may work for you. Try this

[self.tableview setContentOffset:CGPointMake(0, CGFLOAT_MAX)]

Or

if (self.tableview.contentSize.height > self.tableview.frame.size.height) 
    {
        CGPoint offset = CGPointMake(0, self.tableview.contentSize.height -     self.tableview.frame.size.height);
        [self.tableview setContentOffset:offset animated:YES];
    }
Nikunj Rajyaguru
  • 196
  • 1
  • 11
  • 1
    I got other solutions to work but using CGFloat.greatestFiniteMagnitude (swift version of CGFLOAT_MAX) was actually the most efficient since it caused the table view to only call cellForRow(at: IndexPath, ...) for the very bottom cells. Thanks! – Cameron Askew Feb 12 '17 at 21:20
0

Try with scrollToRowAtIndexPath, as mentioned in below code.

- (void)viewWillAppear:(BOOL)animated 
{

    [super viewWillAppear:animated];

    NSInteger no = array.count-1;

    NSIndexPath *lastIndex = [NSIndexPath indexPathForRow:no inSection:0];

   [self.tableView scrollToRowAtIndexPath:lastIndex atScrollPosition:UITableViewScrollPositionBottom animated:YES];

}
Balázs Vincze
  • 3,550
  • 5
  • 29
  • 60
Hasya
  • 9,792
  • 4
  • 31
  • 46
0

I get it work by doing this simple steps:

1) in IB set the estimate height manually to a higher-then-expected value 2) in viewWillAppear scroll to the last row with the ScrollPosition parameter as UITableViewScrollPositionTop

Jim75
  • 737
  • 8
  • 13