为什么插入或删除行时我的UITableView会“跳转”?

(很高兴接受Swift或Objective-C的回答)

我的表视图有几个部分,当按下一个按钮时,我想在第0部分的末尾插入一行。再次按下该按钮,我想删除同一行。 我几乎工作的代码看起来像这样:

// model is an array of mutable arrays, one for each section - (void)pressedAddRemove:(id)sender { self.adding = !self.adding; // this is a BOOL property self.navigationItem.rightBarButtonItem.title = (self.adding)? @"Remove" : @"Add"; // if adding, add an object to the end of section 0 // tell the table view to insert at that index path [self.tableView beginUpdates]; NSMutableArray *sectionArray = self.model[0]; if (self.adding) { NSIndexPath *insertionPath = [NSIndexPath indexPathForRow:sectionArray.count inSection:0]; [sectionArray addObject:@{}]; [self.tableView insertRowsAtIndexPaths:@[insertionPath] withRowAnimation:UITableViewRowAnimationAutomatic]; // if removing, remove the object from the end of section 0 // tell the table view to remove at that index path } else { NSIndexPath *removalPath = [NSIndexPath indexPathForRow:sectionArray.count-1 inSection:0]; [sectionArray removeObject:[sectionArray lastObject]]; [self.tableView deleteRowsAtIndexPaths:@[removalPath] withRowAnimation:UITableViewRowAnimationAutomatic]; } [self.tableView endUpdates]; } 

这有时会正常运行,但有时不会,这取决于滚动表视图的位置:

  • 最顶部的第0部分,contentOffset.y == 0:效果很好,插入行,第0部分下面的内容向下动画
  • 第0部分不可见,因为表格滚过它:效果很好,新行下面的可见内容向下动画,好像在它上面插入了一行。
  • 但是:如果表视图滚动了一点,那么部分0的部分是可见的:它工作错误。 在单个帧中,表视图中的所有内容都会跳转(内容偏移量增加)然后,通过动画,插入新行并且表视图内容向下滚动(内容偏移减少)。 一切都应该到达应有的位置,但是这个过程看起来非常糟糕,单帧开始时会“跳跃”。

我可以通过“Debug-> Toggle Slow Animations”在慢动作模拟器中看到这种情况。 删除时反向出现相同的问题。

我发现偏移量的跳跃大小与表格滚动到第0部分的距离有关:当偏移很小时,跳跃很小。 当滚动接近第0部分总高度的一半时 ,跳跃变大(问题在于它在这里最差,跳跃= =部分高度的一半)。 进一步滚动,跳跃变小。 当滚动表时,只有少量的0部分仍然可见,跳跃很小。

你能帮我理解为什么会这样,以及如何解决?

在iOS 11上,UITableView使用估计的行高作为默认值。

插入/重新加载或删除行时会导致不可预测的行为,因为UITableView在大多数情况下的内容大小错误:

为了避免过多的布局计算,tableView仅为每个cellForRow调用和存储器请求heightForRow (在正常模式下,tableView要求tableFor的所有indexPathes的heightForRow)。 其余单元格的高度等于estimatedRowHeight值,直到调用相应的cellForRow

 // estimatedRowHeight mode contentSize.height = numberOfRowsNotYetOnScreen * estimatedRowHeight + numberOfRowsDisplayedAtLeastOnce * heightOfRow // normal mode contentSize.height = heightOfRow * numberOfCells 

一种解决方案是通过将estimatedRowHeight设置为0并为每个单元格实现heightForRow来禁用estimatedRowHeight模式。

当然,如果您的单元格具有动态高度(大部分时间使用繁重的布局计算,因此您有充分的理由使用了estimatedRowHeight ),则必须找到一种方法来重现estimatedRowHeight优化,而不会影响tableView的contentSize。 看看AsyncDisplayKit或UITableView-FDTemplateLayoutCell 。

另一个解决方案是尝试找到一个很适合的estimatedRowHeight 。 特别是在iOS 11上,您可以尝试对estimatedRowHeight使用UITableViewAutomaticDimension

 tableView.rowHeight = UITableViewAutomaticDimension tableView.estimatedRowHeight = UITableViewAutomaticDimension 

这是在UITableView上发生的,它有多个部分,但没有定义它的标题高度或视图应该是那些部分。 添加以下委托方法为我修复它 – 希望它有所帮助!

 func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 0 } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return nil }