user814037
user814037

Reputation:

scrollToRowAtIndexPath doesn't handle the last row properly

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.

Upvotes: 17

Views: 7721

Answers (4)

kelin
kelin

Reputation: 12011

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.

Upvotes: 0

user814037
user814037

Reputation:

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.

Upvotes: 28

Legoless
Legoless

Reputation: 11112

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

Upvotes: -1

Cyrille
Cyrille

Reputation: 25144

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).

Upvotes: 2

Related Questions