UITableView 滚动到底部搞砸了 EstimatedRowHeight
UITableView scroll to the bottom messed up with EstimatedRowHeight
每当用户创建新评论或刷新 UITableView 时,我都会尝试滚动到 UITableView (commentsFeed) 的底部。
我使用的代码是:
func scrollToBottomOfComments() {
var lastRowNumber = commentsFeed.numberOfRowsInSection(0) - 1
var indexPath = NSIndexPath(forRow: lastRowNumber, inSection: 0)
commentsFeed.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: true)
}
问题出在viewDidLoad
:
commentsFeed.rowHeight = UITableViewAutomaticDimension
commentsFeed.estimatedRowHeight = 150
这基本上表明评论可以具有动态高度,因为用户可以 post 非常长的评论或非常短的评论。
当我使用 estimatedRowHeight 时,我的 scrollToBottom
没有正确滚动到底部,因为它基本上假设我的 table 高度是 commentsFeed.count * commentsFeed.estimatedRowHeight
虽然这是不正确的。
但是,当我删除 estimatedRowHeight
时,它似乎也不起作用,我认为原因是因为它没有正确计算行高,因为每个行都有动态高度。
我该如何缓解这种情况?
编辑:应该指出的是,滚动并没有在正确的位置结束,但是当我用手指滚动到任何地方时,数据就会跳到它应该有的地方通过滚动
有几件事可能会影响您的结果,但最有可能的是您的估计身高不是一个很好的估计。根据我的经验,任何不是特别接近单元格真实高度的东西都会对动画造成严重破坏。在您的情况下,您提到内容是评论或自由格式文本。我猜想这些单元格高度变化很大,并且取决于单元格的组成方式,您可能无法提供非常准确的估计,因此您不应该提供。对于大量单元格,这会损害性能,但您可能别无选择。相反,您可能希望将注意力转移到如何将 in/page 单元格分页到 table 中以避免昂贵的计算,或者重新排列单元格以便能够计算出更好的估计值。另一个建议可能是使用准确但不那么复杂的算法来实现 estimatedHeightForRowAtIndexPath
来计算高度。无论如何,当您支持 DynamicType 时,estimatedRowHeight 的常量值可能永远不会 工作。至少,您需要考虑当前的 DynamicType 大小。
除此之外,你还能做什么?不要使用 UITableViewAutomaticDimension
,而是考虑实现 heightForRowAtIndexPath
并计算显示字符串的高度 并缓存结果 (您可以使用 NSIndexPath -> NSNumber
NSCache
对象)。您需要缓存结果,因为没有 estimatedHeight
,heightForRow
在加载 table 时对每一行调用一次,然后在屏幕上显示的每个单元格调用一次。当在 iOS 8 上使用 estimatedHeight
时,estimatedHeight
在启动时为每个单元格调用一次,而 heightForRow
在单元格出现时调用。这是估算的关键所在,因为这是用来计算 UITableView
支持 UIScrollView
的 contentSize
的。如果估计的大小是错误的,contentSize
是错误的,所以当你要求 tableView 滚动到最后一个单元格时,最后一个单元格的框架是用错误的估计计算的,这给出你错了contentOffset
。不幸的是,我 相信 (基于我看到的尝试重现您的问题的行为)当您使用 UITableViewAutomaticDimension
而不进行估计时,运行时会隐式估计一个估计值。
你为什么不用类似于下面的方法来计算行的实际大小。
- (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;
}
现在滚动,我使用以下来源:
看看吧。
-(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];
}
}
试试这个,对我有用:
NSMutableArray *visibleCellIndexPaths = [[self.tableView indexPathsForVisibleRows] mutableCopy];
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:visibleCellIndexPaths withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
[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];
UIKit 使用 EstimatedRowHeight 来估计整个 contentSize(和 scrollIndicatorInset),因此如果您在末尾使用自动行尺寸添加新行,则必须将 estimatedRowHeight 重置为整个 tableView 的实际平均值在你动画滚动之前。
这不是很好的解决方案,因为在旧样式中手动计算行高要容易得多。或者将新的单元格高度值添加到旧 table 视图的内容高度。
但是因为您在 table 的 end 处添加了新行,您可以在中间或顶部位置滚动,最终到达底部位置,因为是 contentInset.bottom = 0。而且动画看起来会更好。
所以:
commentsFeed.scrollToRowAtIndexPath(indexPath, atScrollPosition: .**Middle**, animated: true)
它看起来像在.Middle 和.Top 位置是动画引擎盖下的一些条件,防止在底部边缘和table 视图内容之间形成间隙(+ contentInset.bottom)
P.S。 为什么不使用手动行高计算?
因为有自动布局,我相信 "auto" 是自动的快捷方式。此外,它还可以节省您的时间和使用自定义字体、属性字符串、带有更多标签的组合单元格和其他子视图等的麻烦..
每当用户创建新评论或刷新 UITableView 时,我都会尝试滚动到 UITableView (commentsFeed) 的底部。
我使用的代码是:
func scrollToBottomOfComments() {
var lastRowNumber = commentsFeed.numberOfRowsInSection(0) - 1
var indexPath = NSIndexPath(forRow: lastRowNumber, inSection: 0)
commentsFeed.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: true)
}
问题出在viewDidLoad
:
commentsFeed.rowHeight = UITableViewAutomaticDimension
commentsFeed.estimatedRowHeight = 150
这基本上表明评论可以具有动态高度,因为用户可以 post 非常长的评论或非常短的评论。
当我使用 estimatedRowHeight 时,我的 scrollToBottom
没有正确滚动到底部,因为它基本上假设我的 table 高度是 commentsFeed.count * commentsFeed.estimatedRowHeight
虽然这是不正确的。
但是,当我删除 estimatedRowHeight
时,它似乎也不起作用,我认为原因是因为它没有正确计算行高,因为每个行都有动态高度。
我该如何缓解这种情况?
编辑:应该指出的是,滚动并没有在正确的位置结束,但是当我用手指滚动到任何地方时,数据就会跳到它应该有的地方通过滚动
有几件事可能会影响您的结果,但最有可能的是您的估计身高不是一个很好的估计。根据我的经验,任何不是特别接近单元格真实高度的东西都会对动画造成严重破坏。在您的情况下,您提到内容是评论或自由格式文本。我猜想这些单元格高度变化很大,并且取决于单元格的组成方式,您可能无法提供非常准确的估计,因此您不应该提供。对于大量单元格,这会损害性能,但您可能别无选择。相反,您可能希望将注意力转移到如何将 in/page 单元格分页到 table 中以避免昂贵的计算,或者重新排列单元格以便能够计算出更好的估计值。另一个建议可能是使用准确但不那么复杂的算法来实现 estimatedHeightForRowAtIndexPath
来计算高度。无论如何,当您支持 DynamicType 时,estimatedRowHeight 的常量值可能永远不会 工作。至少,您需要考虑当前的 DynamicType 大小。
除此之外,你还能做什么?不要使用 UITableViewAutomaticDimension
,而是考虑实现 heightForRowAtIndexPath
并计算显示字符串的高度 并缓存结果 (您可以使用 NSIndexPath -> NSNumber
NSCache
对象)。您需要缓存结果,因为没有 estimatedHeight
,heightForRow
在加载 table 时对每一行调用一次,然后在屏幕上显示的每个单元格调用一次。当在 iOS 8 上使用 estimatedHeight
时,estimatedHeight
在启动时为每个单元格调用一次,而 heightForRow
在单元格出现时调用。这是估算的关键所在,因为这是用来计算 UITableView
支持 UIScrollView
的 contentSize
的。如果估计的大小是错误的,contentSize
是错误的,所以当你要求 tableView 滚动到最后一个单元格时,最后一个单元格的框架是用错误的估计计算的,这给出你错了contentOffset
。不幸的是,我 相信 (基于我看到的尝试重现您的问题的行为)当您使用 UITableViewAutomaticDimension
而不进行估计时,运行时会隐式估计一个估计值。
你为什么不用类似于下面的方法来计算行的实际大小。
- (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;
}
现在滚动,我使用以下来源: 看看吧。
-(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];
}
}
试试这个,对我有用:
NSMutableArray *visibleCellIndexPaths = [[self.tableView indexPathsForVisibleRows] mutableCopy];
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:visibleCellIndexPaths withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
[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];
UIKit 使用 EstimatedRowHeight 来估计整个 contentSize(和 scrollIndicatorInset),因此如果您在末尾使用自动行尺寸添加新行,则必须将 estimatedRowHeight 重置为整个 tableView 的实际平均值在你动画滚动之前。
这不是很好的解决方案,因为在旧样式中手动计算行高要容易得多。或者将新的单元格高度值添加到旧 table 视图的内容高度。
但是因为您在 table 的 end 处添加了新行,您可以在中间或顶部位置滚动,最终到达底部位置,因为是 contentInset.bottom = 0。而且动画看起来会更好。
所以:
commentsFeed.scrollToRowAtIndexPath(indexPath, atScrollPosition: .**Middle**, animated: true)
它看起来像在.Middle 和.Top 位置是动画引擎盖下的一些条件,防止在底部边缘和table 视图内容之间形成间隙(+ contentInset.bottom)
P.S。 为什么不使用手动行高计算?
因为有自动布局,我相信 "auto" 是自动的快捷方式。此外,它还可以节省您的时间和使用自定义字体、属性字符串、带有更多标签的组合单元格和其他子视图等的麻烦..