自己贡献答案, dispatch_group_t downloadGroup = dispatch_group_create();
dispatch_group_enter(downloadGroup); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self refreshTableView]; dispatch_group_leave(downloadGroup); }); dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{ [self scrollToMessageBottom]; });
Most likely reloadData doesn't fully execute when it gets called, because it assumes you might make more changes and tell it to reload again.
Try introducing a zero second delay, to give it a chance to run:
//意思是 tableView还没刷新完就开始调用滚到到底部的方法,所以可以利用伪延迟来进行处理。
//添加数据刷新
[_items addObject:[NSString stringWithFormat:@"Trawl %d", count]];
[self.postsTableView beginUpdates];
[self.postsTableView insertRowsAtIndexes:[NSIndexSet indexSetWithIndex:_items.count - 1] withAnimation:NSTableViewAnimationEffectNone]; // maybe you want some other animation here
[self.postsTableView endUpdates];
double delayInSeconds = 0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self.tbl scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:self.items.count - 1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
});
我的情况按照这种方法解决了。
http://stackoverflow.com/questions/21371351/uitableview-not-scrolling-to-bottom
http://stackoverflow.com/questions/25686490/ios-8-auto-cell-height-cant-scroll-to-last-row
I am using iOS 8 new self-sizing cells. Visually it works good - each cell gets its right size. However, if I try to scroll to the last row, the table view doesn't seem to know its right size. Is this a bug or is there a fix for that?
Here's how to recreate the problem:
Using this project - TableViewCellWithAutoLayoutiOS8 (referenced from this SO answer), I got the auto-resizing cells as expected.
However, if I am calling the scrollToRowAtIndexPath function, like this:
tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: model.dataArray.count - 1, inSection: 0), atScrollPosition: .Bottom, animated: true)
I do not get to the last row - It only gets me around halfway there.
Even by trying to use a lower level function like this:
tableView.setContentOffset(CGPointMake(0, tableView.contentSize.height - tableView.frame.size.height), animated: true)
The result is not as expected, it won't get to the end. If I click it a lot of times or wait a few moments, eventually it will get to the right place. It seems the tableView.contentSize.height is not set correctly, so the iOS "doesn't know" where that last cell is.
Would appreciate any help.
Thanks
-
Some questions: Does it work for rows towards the beginning? Is there a cut off point where it stops working? Is it a problem with one section or all sections? Does your auto layout have any errors? Is your estimated cell height very different from your cell's actual height? Finally, if you have a repeatable bug then please report it to: bugreport.apple.com – Robotic Cat Sep 5 '14 at 13:30
-
It's not accurate for any row index I insert. Tried it with only 1 section. No errors reported in IB/console. Estimated height seems to be about the average. Seems like a bug I think, but maybe this post will help :) Thanks. – BenB Sep 5 '14 at 13:59
-
even the ios 8 & Xcode GM seed didn't fix this one :( – BenB Sep 10 '14 at 20:52
-
Time to log a bug although I am surprised that no-one else has come across this. It's not an uncommon pattern. – Robotic Cat Sep 10 '14 at 22:28
-
1I'm having the same issue. One additional side-effect of this bug is that when you scroll to the "bottom" (or somewhere close to that) and then you scroll up, the table view jumps when recalculating the correct cell size from the top rows. This happens if you scroll:animated NO (which doesn't give time for the table view to load the top cells). – Guilherme Sprint Oct 14 '14 at 0:43
Update: Jun 24, 2015
Apple has addressed most of these bugs as of the iOS 9.0 SDK. All of the issues are fixed as of iOS 9 beta 2, including scrolling to the top & bottom of the table view without animation, and calling reloadData
while scrolled in the middle of the table view.
Here are the remaining issues that have not been fixed yet:
- When using a large estimated row height, scrolling to the last row with animation causes the table view cells to disappear.
- When using a small estimated row height, scrolling to the last row with animation causes the table view to finish scrolling too early, leaving some cells below the visible area (and the last row still offscreen).
A new bug report (rdar://21539211) has been filed for these issues relating to scrolling with animation.
Original Answer
This is an Apple bug with the table view row height estimation, and it has existed since this functionality first was introduced in iOS 7. I have worked directly with Apple UIKit engineers and developer evangelists on this issue -- they have acknowledged that it is a bug, but do not have any reliable workaround (short of disabling row height estimation), and did not seem particularly interested in fixing it.
Note that the bug manifests itself in other ways, such as disappearing table view cells when you call reloadData
while scrolled partially or fully down (e.g. contentOffset.y
is significantly greater than 0).
Clearly, with iOS 8 self sizing cells, row height estimation is critically important, so Apple really needs to address this ASAP.
I filed this issue back on Oct 21 2013 as Radar #15283329. Please do file duplicate bug reports so that Apple prioritizes a fix.
You can attach this simple sample project to demonstrate the issue. It is based directly on Apple's own sample code.
-
1Or you may use github.com/slackhq/SlackTextViewController and get all over the problem we're still waiting Apple to fix – abinop Oct 8 '14 at 6:19
-
2@abinop How does Slack's text view controller provide a general case substitute/workaround for a UITableView? I think that's only appropriate for some specific use cases, and certainly not going to be worth reimplementing existing code. – smileyborg Oct 8 '14 at 6:44
-
it depends on how bad one needs the list to scroll down and also knowing that the problem exists for so much time and there might not be a solution soon.I am going to try for myself to extend Slack and see how it goes.It may be useful for someone who is in the start of developing a project. – abinop Oct 8 '14 at 6:55
-
1@abinop IIRC they use an upside down table view in their internal implementation. I wouldn't get too excited :) – smileyborg Oct 8 '14 at 6:57
-
I created an example that shows clearly what the problem is, can be found here github.com/abinop/Auto-height-ios8-UITableView-scroll-problem .I will post it as a bug report. – abinop Oct 10 '14 at 13:27
This has been a very annoying bug, but I think I found a permanent solution, though I cannot fully explain why.
Call the function after a tiny (unnoticed) delay:
let delay = 0.1 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue(), {
tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: model.dataArray.count - 1, inSection: 0), atScrollPosition: .Bottom, animated: true)
})
Do tell me if this works for you as well.
-
This worked for me. It allows my call to
reloadData
to finish before trying to reset scroll positions. I am using iOS 8.1 with auto-height cells. – Eric Baker Jan 10 '15 at 19:56 -
6This does not seem to work for me, even with a greater time delay. =( – XerionFeb 19 '15 at 19:41
-
This worked for me, but only if I did it twice. Once right away to go to the wrong location, then after a delay. I'm not sure how reliable it is. stackoverflow.com/questions/28613995/… – iseletsky Feb 20 '15 at 0:49
-
Didn't work for me, the scrolling was worse. – Stephan Mar 31 '15 at 12:32
-
yes this worked, however i try to make less dirty code : dispatch_after(0, dispatch_get_main_queue(), ^{ [_table scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:_myGroup.shouts.count-1 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:NO]; }); – Alok May 26 '15 at 6:20
It is definitely a bug from Apple. I also have this problem. I solved this problem by calling "scrollToRowAtIndexPath" method twice example code is:
if array.count > 0 {
let indexPath: NSIndexPath = NSIndexPath(forRow: array.count - 1, inSection: 0)
self.tblView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: true)
let delay = 0.1 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue(), {
self.tblView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: true)
})
}
-
This workaround works perfectly. Thanks! – neevek Jul 13 '15 at 11:53
-
3This worked for me with iOS9, thanks! I revised it slightly to use
dispatch_async(dispatch_get_main_queue()...
instead, that way there is no determinable delay, it's just when the main queue is free. – Chris Conway Jun 2 '16 at 5:44 -
I can confirm that this is the ONLY way to walk around (iOS9~iOS11 tested), thank you, save me the day! – yuchen May 30 at 8:11
I found a temporary workaround that might be helpful until Apple decides to fixes the many bugs that have been plaguing us.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *text = [self findTextForIndexPath:indexPath];
UIFont *font = [UIFont fontWithName:@"HelveticaNeue" size:13];
CGRect estimatedHeight = [text boundingRectWithSize:CGSizeMake(215, MAXFLOAT)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName: font}
context:nil];
return TOP_PADDING + CGRectGetHeight(estimatedHeight) + BOTTOM_PADDING;
}
This is not perfect, but it did the job for me. Now I can call:
- (void)scrollToLastestSeenMessageAnimated:(BOOL)animated
{
NSInteger count = [self tableView:self.tableView numberOfRowsInSection:0];
if (count > 0) {
NSInteger lastPos = MAX(0, count-1);
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:lastPos inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:animated];
}
}
On viewDidLayoutSubviews
and it finds the correct place on the bottom (or a very close estimated position).
I hope that helps.
-
I'm looking since days for a solution, but this is honestly the best workaround! Thanks! – cldrr Jul 16 '15 at 9:58
For my case, I found a temporary workaround by not suggesting an estimated cell height to the program. I did this by commenting out the following method in my code:
- (CGFloat) tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
However, please take note that doing so may affect the user experience when the user scrolls, if your cells varies a lot compared to each other. For my case, no noticeable difference so far.
Hope it helps!
My solution was to use the size of the storyboard as the estimate.
So instead of this:
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
I did something like this:
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
MyMessageType messageType = [self messageTypeForRowAtIndexPath:indexPath];
switch (messageType) {
case MyMessageTypeText:
return 45;
break;
case MyMessageTypeMaybeWithSomeMediaOrSomethingBiggerThanJustText:
return 96;
break;
default:
break;
}
}
I'm writing a chat table view so it is likely that many of my cells, specifically that text type will be larger than what is in IB, especially if the chat message is very long. This seems to be a pretty good...well...estimate and scrolling to the bottom gets pretty close. It seems to be slightly worse as the scrolling gets longer, but that is to be expected I suppose
Just call tableview reloadData after viewDidAppear can solve the problem
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.tableView reloadData];
}
From the storyboard window click in a blank area to deselect all views then click the view that has the table view in it and then click the Resolve Auto Layout Issue
icon and select Reset to Suggested Constraints
Although as smileyborg's answer it is bug in iOS 8.x, it should be fixed in all platforms which you supports...
To workaround on pre-iOS9, below code do the trick without any dispatch_async or dispatch_after. Tested on iOS 8.4 simulator.
UPDATE: Calling (only) layoutIfNeeded does not work when view controller become visible by UIPageViewController being scrolled. So use layoutSubviews (or maybe setNeedsLayout + layoutIfNeeded) instead.
// For iOS 8 bug workaround.
// See https://stackoverflow.com/a/33515872/1474113
- (void)scrollToBottomForPreiOS9
{
CGFloat originalY, scrolledY;
do {
// Lay out visible cells immediately for current contentOffset.
// NOTE: layoutIfNeeded does not work when hosting UIPageViewController is dragged.
[self.tableView layoutSubviews];
originalY = self.tableView.contentOffset.y;
[self scrollToBottom]; // Call -scrollToRowAtIndexPath as usual.
scrolledY = self.tableView.contentOffset.y;
} while (scrolledY > originalY);
}
I had the same problem when creating a chat tableView with different height of cells. I call the code below in viewDidAppear() lifecycle method:
// First figure out how many sections there are
let lastSectionIndex = self.tableView.numberOfSections - 1
// Then grab the number of rows in the last section
let lastRowIndex = self.tableView.numberOfRowsInSection(lastSectionIndex) - 1
// Now just construct the index path
let pathToLastRow = NSIndexPath(forRow: lastRowIndex, inSection: lastSectionIndex)
// Make the last row visible
self.tableView.scrollToRowAtIndexPath(pathToLastRow, atScrollPosition: UITableViewScrollPosition.None, animated: true)
Please let me know if that worked for you too.
Use this simple code to scroll bottom
var rows:NSInteger=self.tableName.numberOfRowsInSection(0)
if(rows > 0)
{
let indexPath = NSIndexPath(forRow: rows-1, inSection: 0)
tableName.scrollToRowAtIndexPath(indexPath , atScrollPosition: UITableViewScrollPosition.Bottom, animated: true)
}
}
-
2The question is tagged "Swift", not "Objective-C". Please always provide an answer in the tagged language. Thank you. – Moritz Apr 18 '16 at 14:07
-
The question already said that this approach doesn't work for cell with dynamic height – Daron Tancharoen Aug 10 '16 at 10:18
-
ok now i change my code to swift – Bibin Joseph Aug 10 '16 at 10:43
protected by Community♦ May 10 '15 at 23:49
Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).
Would you like to answer one of these unanswered questions instead?
Not the answer you're looking for? Browse other questions tagged ios uitableview swift ios8 or ask your own question.
Hi I'm going to link you a video showcasing my problem as it's somewhat hard to convey with words. UITableView not scrolling to bottom In the video I'm hitting a button which adds items to the list. Note that I am not stopping at Item 16, and that I am still adding more items to the list that don't get displayed. Eventually, upon added new items, the list will automatically scroll up via
[_items addObject:[NSString stringWithFormat:@"Trawl %d", count]];
[self.tbl reloadData];
[self.tbl scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:self.items.count - 1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
The main issue is towards the end of the video, where I scroll towards the last few items, but I cannot select them. Apparently the issue has to deal with the height of the UITableView. I've tried changing this in the storyboard editor but it didn't do anything. Any ideas? Thanks in advance!
-
I have it in an IBAction for an "Add" button. It's the one I'm pressing in the video to add items into the table. – user2402616 Jan 27 '14 at 0:53
-
1This is probably an issue with the
UITableView
's contentSize property being incorrect. I think I had the same problem recently, put some debugging code in to see if your content size is being updated when you add the new cells – Nic Robertson Jan 27 '14 at 0:54 -
The content size's height gets updated by 44 (default row height) each time. Obviously the width stays the same. The height starts at 109, which is where the table starts apparently. Now it only starts to automatically scroll down once the height surpasses 1024 (the height for the iPad mini). However, from height 813-1033 no new items show up. Any idea? – user2402616 Jan 27 '14 at 2:07
-
Your table view my be larger than the device screen size, try reducing the size of your
UITableView
. – Nic Robertson Jan 27 '14 at 2:16
Ok from what I can see and from the comments, I think it is likely that the height of your UITableView
is larger than the device screen size, which means it will not scroll down because the items haven't reached the bottom of the UITableView
's UIScrollView
.
Go to IB and set constraints so the UITableView
is equal to the view controllers width and height.
-
I did that but then it overlaps all the elements in the top. IE. the buttons in the video. Also the "view" mode is on "scale to fill" under attributes – user2402616Jan 27 '14 at 2:52
-
1Oh sorry forgot about the buttons. Remove the constraints and set new ones to Top and bottom space to container and the width (or leading & trailing space) – Nic Robertson Jan 27 '14 at 2:55
-
1I can't see anything there – Nic Robertson Jan 27 '14 at 3:19
-
1Ok have you got the constraint Bottom space to bottom layout guide? – Nic Robertson Jan 27 '14 at 3:21
-
1let us continue this discussion in chat – Nic Robertson Jan 27 '14 at 3:25
Most likely reloadData
doesn't fully execute when it gets called, because it assumes you might make more changes and tell it to reload again.
Try introducing a zero second delay, to give it a chance to run:
[_items addObject:[NSString stringWithFormat:@"Trawl %d", count]];
[self.tbl reloadData];
double delayInSeconds = 0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self.tbl scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:self.items.count - 1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
});
Also, you should not be using reloadData
in this situation. This is how rows should be added:
[_items addObject:[NSString stringWithFormat:@"Trawl %d", count]];
[self.postsTableView beginUpdates];
[self.postsTableView insertRowsAtIndexes:[NSIndexSet indexSetWithIndex:_items.count - 1] withAnimation:NSTableViewAnimationEffectNone]; // maybe you want some other animation here
[self.postsTableView endUpdates];
double delayInSeconds = 0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self.tbl scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:self.items.count - 1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
});
-
I get use of undeclared identifier for "NSTableViewAnimationEffectNone". Also, the introduced delay had no effect. – user2402616 Jan 27 '14 at 1:21
-
1Sorry, it's probably
UI
instead ofNS
for the animation, check the table view documentation. You should be using that instead of reloadData. – Abhi BeckertJan 27 '14 at 1:27 -
1Not sure what your problem is if introducing a delay didn't fix it, maybe @Nic Robertson is correct in which case it sounds like a bug Apple needs to fix... and you need to find a workaround until then. – Abhi Beckert Jan 27 '14 at 1:27
-
Yeah looks like that's my only option. Thanks though! – user2402616 Jan 27 '14 at 1:34
-
Thanks @AbhiBeckert worked like a charm. – KarunPant75 Jan 20 '16 at 11:30
Seems most of you are wrong.
It should be like this:
[self.tbl scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:self.items.count - 1 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
That is, should be UITableViewScrollPositionTop, not UITableViewScrollPositionBottom. Here atScrollPosition:UITableViewScrollPositionTop is the origin of change, not the destination.