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 对象)。您需要缓存结果,因为没有 estimatedHeightheightForRow 在加载 table 时对每一行调用一次,然后在屏幕上显示的每个单元格调用一次。当在 iOS 8 上使用 estimatedHeight 时,estimatedHeight 在启动时为每个单元格调用一次,而 heightForRow 在单元格出现时调用。这是估算的关键所在,因为这是用来计算 UITableView 支持 UIScrollViewcontentSize 的。如果估计的大小是错误的,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" 是自动的快捷方式。此外,它还可以节省您的时间和使用自定义字体、属性字符串、带有更多标签的组合单元格和其他子视图等的麻烦..