I'm trying to scroll to the bottom of my UITableView (commentsFeed) whenever the user creates a new comment or the user refreshes the UITableView.
The code I use is:
func scrollToBottomOfComments() {
var lastRowNumber = commentsFeed.numberOfRowsInSection(0) - 1
var indexPath = NSIndexPath(forRow: lastRowNumber, inSection: 0)
commentsFeed.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: true)
}
The problem is here in viewDidLoad
:
commentsFeed.rowHeight = UITableViewAutomaticDimension
commentsFeed.estimatedRowHeight = 150
This basically states that the comments can have dynamic heights because users could post either really long comments or really short comments.
When I use the estimatedRowHeight, my scrollToBottom
doesn't properly scroll to the bottom because it basically assumes my table height is commentsFeed.count * commentsFeed.estimatedRowHeight
This isn't correct though.
When I remove the estimatedRowHeight
though, it doesn't seem to work either, and I think the reason is because it doesn't have the row height calculated properly because the rows each have dynamic heights.
How do I mitigate this?
Edit: It should be stated that the scroll doesn't end up at the right position, but the moment I use my finger to scroll anywhere, then the data jumps into place where it should have been via the scroll
Why don't you calculate the real size of row by something similar to below method.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CustomObject *message = [list.fetchedObjects objectAtIndex:indexPath.row];
//fontChat not available yet
NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithString:message];
NSRange all = NSMakeRange(0, text.length);
[text addAttribute:NSFontAttributeName value:[UIFont fontWithName:DEFAULT_FONT size:21] range:all];
[text addAttribute:NSForegroundColorAttributeName value:RGB(61, 61, 61) range:all];
CGSize theSize = [text boundingRectWithSize:CGSizeMake(200, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin context:nil].size;
if (theSize.height == 0) {
theSize.height = FIXED_SIZE;
}
return theSize.height;
}
Now for scrolling, I use the following source: Check it out.
-(void) scrollTolastRow
{
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];
}
}
There are a couple things that could be affecting your outcome, but it's most likely that your estimated height is not a great estimate. In my experience, anything not particularly close to the true height of the cells will cause havoc on animations. In your case, you mention that the content are comments, or free-form text. I would guess that these cell heights vary wildly, and depending on how your cell is composed, you're probably not going to be able to provide a very accurate estimate, and so you should not provide one. For a large number of cells, this is going to hurt performance, but you probably don't have a choice. Instead, you might want to shift your focus to how you can page in/page out cells into your table to avoid costly calculations, or rearrange your cell to be able to calculate a better estimate. Another suggestion might be to implement estimatedHeightForRowAtIndexPath
with an accurate but still less complex algorithm for calculating the height. In any event, a constant value for an estimatedRowHeight will likely never work when you support DynamicType. At the very least, you need to take into account the current DynamicType size.
Other than that, what can you do? Instead of using UITableViewAutomaticDimension
, consider implementing heightForRowAtIndexPath
and calculating the height of your displayed strings and caching the result (you could use an NSIndexPath -> NSNumber
NSCache
object). You need to cache the result because without an estimatedHeight
, heightForRow
is called once for every row when the table is loaded, and then once for every cell as it appears on screen. When using estimatedHeight
on iOS 8, estimatedHeight
is called once for each cell on launch and heightForRow
is called as the cells appear. This is where the estimate is critical, because that is what's used to calculate the contentSize
of the UITableView
's backing UIScrollView
. If the estimated size is wrong, the contentSize
is wrong, and so when you ask the tableView to scroll to the last cell, the frame of the last cell is calculated with the bad estimate, which gives you the incorrect contentOffset
. Unfortunately, I believe (based on the behavior I see trying to reproduce your question) that when you use UITableViewAutomaticDimension
without an estimate, the runtime implicitly estimates an estimate.
EstimatedRowHeight is used by UIKit to estimate whole contentSize (and scrollIndicatorInset), so if you add new row at the end with automatic row dimension, you have to reset estimatedRowHeight to actual average value of whole tableView before you animate scroll.
This is not so good solution because its lot easier to count row height in old style - manually. Or add new cell height value to old table view's content height.
But because you adding new row at the end of table, you can scroll at middle or top position, which ends up with the bottom position, because there is contentInset.bottom = 0. And also the animation will look better.
So:
commentsFeed.scrollToRowAtIndexPath(indexPath, atScrollPosition: .**Middle**, animated: true)
Its look like in .Middle and .Top positions is some condition under the hood in animation which prevent to make a gap between bottom edge and table view's content (+ contentInset.bottom)
P.S. Why not to use the manuall row height calculation?
Because there is autolayout and I believe the "auto" is shortcut for automatic. And also it will save your time and troubles with custom fonts, attributed string, combined cell with more labels and other subviews and so on..
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With