17

I'm having an issue with UITableView where it doesn't seem to handle scrolling to the last row properly when using scrollToRowAtIndexPath:atScrollPosition:animated

Here's the code I'm using to cause the scroll:

[self.tableView scrollToRowAtIndexPath:indexPath
                      atScrollPosition:UITableViewScrollPositionMiddle 
                              animated:YES];

And here's a screenshot showing the result (and the issue!):

UITableView scroll issue screenshot

As you can see, the very last row (September) isn't scrolled fully into view; the bottom few pixels are cut-off.

I've tried using UITableViewScrollPositionNone, UITableViewScrollPositionTop and UITableViewScrollPositionBottom as the scroll position but they all produce the same result.

My cell does have a custom cell height of 61.0f which is currently set in the storyboard, but adding the UITableViewDelegate method tableView:heightForRowAtIndexPath: and returning the same value doesn't help either.

Is there any way I can get the table view to scroll to the last row AND have it fully visible?

EDIT:

Just to be clear, I'm using a stock UINavigationController with a stock UITableViewController as it's root view controller.

EDIT 2:

If I use rectForRowAtIndexPath: to determine the rect for the row, it does in-fact return the correct rect for that row. But if I then call scrollRectToVisible:animated: using that rect, I get the same result as above; the bottom few pixels are cut-off.

  • What's your table view's frame, what are its content insets? – Aaron Brager Mar 14 '14 at 13:34
  • I'll check. But like I said, I aren't manipulating those; I've just dragged a UITableViewController from the palette onto the canvas in IB. –  Mar 14 '14 at 13:36
  • 1
    Are you able to manually scroll to the last cell and see it in it's entirety? – mharper Mar 14 '14 at 13:39
  • @mharper I certainly am. Also, if I flick the table view so it scrolls to the very bottom, when it's done bouncing and comes to a stop, the last row is fully in view. –  Mar 14 '14 at 13:41
  • When are you scrolling to the bottom? You're sure you're scrolling to the last row? Please show some code. – Marcus Adams Mar 14 '14 at 13:50
  • It's called from viewWillAppear and a private method which is triggered when the underlying data source changes. –  Mar 14 '14 at 13:52
  • Are you using AutoLayout? If so, have you pinned the table view to the bottom of it's super view? – Aaron Mar 14 '14 at 21:06
  • I am using AutoLayout. I haven't touched the layout of the table view since it's a stock UITableViewController added from the palette in IB. I've not added the table view manually. –  Mar 14 '14 at 21:08

4 Answers4

28

Okay, I've successfully been able to fix this issue.

The clue was in this comment by Matt Di Pasquale on a semi-related question.

It turns out that in iOS 7, the views are yet to be laid out when viewWillAppear: is called, meaning the frame and bounds of the table view are not guaranteed to be accurate. Since the call to scrollToRowAtIndexPath:animated must use at least one of these to calculate the offset, this makes sense as to why in my case it wasn't being handled properly.

I think in most cases people won't encounter this issue as their presenting view controller will likely have the same bounds as the presented view controller. But in my case, I was presenting a view controller that had a visible navigation bar and status bar from one that didn't have either, ergo there was an extra 64pts to account for. As can be seen in this console output:

2014-03-15 19:14:32.129 Capture[3375:60b] viewWillAppear, tv bounds: {{0, 0}, {320, 568}}
2014-03-15 19:14:32.131 Capture[3375:60b] viewDidLayoutSubviews, tv bounds: {{0, -64}, {320, 568}}

To get around the issue, I now set a flag in viewWillAppear: that signifies there's a pending scroll and then in viewDidLayoutSubviews, if the flag is set, I call scrollToRowAtIndexPath:animated and unset the flag (since this method is called numerous times). This works flawlessly.

Hopefully this will help anyone else coming across the issue.

Community
  • 1
  • 1
  • I was forced to use manual offset calculations since iOS7 is out until today I see this!! Wish I had read this post earlier! Thanks! – Anthony Feb 24 '15 at 02:12
  • Nice suggestion but does not work for me. I am loading the data in a separate thread. Once the data is loaded I call `[mytableView reload]`. And that does make a call to `viewDidLayoutSubviews`. Still fishing for an solution - do not want to manually adjust. – Shirish Kumar Apr 18 '15 at 01:55
2

What about using a lower-level function? After all, a UITableView is a UIScrollView.

[self.tableView setContentOffset:(CGPoint){0, self.tableView.contentSize.height - self.tableView.bounds.size.height} animated:YES];

(Adjust the offset as desired if you have content edge insets other than 0).

Cyrille
  • 25,014
  • 12
  • 67
  • 90
  • Worked for me, but had to adjust to `[self.tableView setContentOffset:(CGPoint){0, self.tableView.contentSize.height} animated:YES];` – Cbas Dec 07 '15 at 12:12
  • could you explain what you mean by low-level function? are you saying the standard scrollToRow not reliable? – user1019042 Mar 29 '17 at 00:37
0

I've noticed the same bug when I was scrolling to the particular row of the tableView when user touches back button. I noticed that contentInset of the table view is changing during the navigation (due to automaticallyAdjustsScrollViewInsets = true). The only solution I found is to add an observer:

tableView.addObserver(self, forKeyPath: "contentInset", options: .new, context: nil)

and perform the scrollToRow method after the contentInset establishes. This is not a beautiful solution, but it works.

kelin
  • 11,323
  • 6
  • 67
  • 104
-1

I would also suggest you turn of a UIViewController property, which is on by default:

@property (nonatomic, assign) BOOL automaticallyAdjustsScrollViewInsets;

This one has helped me with many issues, when I did not know why UITableView (which is a subclass of UIScrollView actually) was not scrolling properly. Depending on your layout of course.

This property is available since iOS 7, read more about it here:

http://b2cloud.com.au/general-thoughts/uiviewcontroller-changes-in-ios7

Legoless
  • 10,942
  • 7
  • 48
  • 68
  • This property was added because on iOS 7 the views automatically extend under the navigation and toolbars, and that's behaviour I want. The scroll methods should take this into account in their internal implementation. –  Mar 14 '14 at 21:06
  • 1
    I do not think this property affect that as much. In my case it only moved the scrollView inset for 20pt, which is the size of Status bar, not navigation or toolbar. – Legoless Mar 14 '14 at 23:52
  • Deprecated since iOS 11, is now likely `scrollView.contentInsetAdjustmentBehavior = .automatic` if yes before – Jonny Dec 22 '17 at 09:48
  • Actually, it did not help me either. – Jonny Dec 22 '17 at 09:58