UITableView布局搞乱推继续和返回。 (iOS 8,Xcode beta 5,Swift)

tldr; 自动约束在push segue上显示为中断,并返回查看自定义单元格

编辑 :我提供了一个github示例项目,展示了发生的错误https://github.com/Matthew-Kempson/TableViewExample.git

我正在创build一个应用程序,它需要自定义UITableCell的标题标签以允许根据post标题的长度来改变行数。 单元格正确加载到视图中,但是如果按下单元格将其加载到包含WKWebView的视图中,您可以看到,如屏幕截图所示,单元格会立即移动到不正确的位置。 当通过UINavigationController的后退button加载视图时,也可以看到这一点。

在这个特别的例子中,我在最后一个单元格上按下“我在巴黎拍了一张照片的两个哥们”的标题,并且正确地加载了一切。 然后如下图所示,在加载第二个视图的背景下,单元格全部向上移动,原因不明。 然后,当我加载视图时,你可以看到屏幕稍微向上移动,我实际上不能滚动任何低于所示。 与其他testing一样,当视图加载到底部单元格下方的空白处不会消失时,这看起来是随机的。

我还包括一个包含细胞所具有的限制的图片。

图像(我需要更多的声誉提供图像在这个问题显然,所以他们在这张imgur专辑): http ://imgur.com/a/gY87E

我的代码:

自定义单元格中的方法允许单元格在旋转时正确调整视图的大小:

override func layoutSubviews() { super.layoutSubviews() self.contentView.layoutIfNeeded() // Update the label constaints self.titleLabel.preferredMaxLayoutWidth = self.titleLabel.frame.width self.detailsLabel.preferredMaxLayoutWidth = self.detailsLabel.frame.width } 

在tableview中的代码

 override func viewDidLoad() { super.viewDidLoad() // Create and register the custom cell self.tableView.estimatedRowHeight = 56 self.tableView.rowHeight = UITableViewAutomaticDimension } 

代码来创build单元格

  override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! { if let cell = tableView.dequeueReusableCellWithIdentifier("LinkCell", forIndexPath: indexPath) as? LinkTableViewCell { // Retrieve the post and set details let link: Link = self.linksArray.objectAtIndex(indexPath.row) as Link cell.titleLabel.text = link.title cell.scoreLabel.text = "\(link.score)" cell.detailsLabel.text = link.stringCreatedTimeIntervalSinceNow() + " ago by " + link.author + " to /r/" + link.subreddit return cell } return nil } 

如果您需要更多的代码或信息,请询问我将提供什么是必要的

谢谢你的帮助!

这种行为的问题是,当你推入一个segue时, tableView将调用可见单元格的estimatedHeightForRowAtIndexPath ,并将单元格高度重置为默认值。 这发生在viewWillDisappear调用之后。 如果你回来的TableView所有可见的单元格搞砸了..

我用estimatedCellHeightCache CellHeightCache解决了这个问题。 我只是简单的将这段代码添加到cellForRowAtIndexPath方法中:

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { ... // put estimated cell height in cache if needed if (![self isEstimatedRowHeightInCache:indexPath]) { CGSize cellSize = [cell systemLayoutSizeFittingSize:CGSizeMake(self.view.frame.size.width, 0) withHorizontalFittingPriority:1000.0 verticalFittingPriority:50.0]; [self putEstimatedCellHeightToCache:indexPath height:cellSize.height]; } ... } 

现在您必须实现estimatedHeightForRowAtIndexPath ,如下所示:

 -(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath { return [self getEstimatedCellHeightFromCache:indexPath defaultHeight:41.5]; } 

configurationcaching

将此属性添加到.h文件中:

 @property NSMutableDictionary *estimatedRowHeightCache; 

实现方法put / get / reset ..caching:

 #pragma mark - estimated height cache methods // put height to cache - (void) putEstimatedCellHeightToCache:(NSIndexPath *) indexPath height:(CGFloat) height { [self initEstimatedRowHeightCacheIfNeeded]; [self.estimatedRowHeightCache setValue:[[NSNumber alloc] initWithFloat:height] forKey:[NSString stringWithFormat:@"%d", indexPath.row]]; } // get height from cache - (CGFloat) getEstimatedCellHeightFromCache:(NSIndexPath *) indexPath defaultHeight:(CGFloat) defaultHeight { [self initEstimatedRowHeightCacheIfNeeded]; NSNumber *estimatedHeight = [self.estimatedRowHeightCache valueForKey:[NSString stringWithFormat:@"%d", indexPath.row]]; if (estimatedHeight != nil) { //NSLog(@"cached: %f", [estimatedHeight floatValue]); return [estimatedHeight floatValue]; } //NSLog(@"not cached: %f", defaultHeight); return defaultHeight; } // check if height is on cache - (BOOL) isEstimatedRowHeightInCache:(NSIndexPath *) indexPath { if ([self getEstimatedCellHeightFromCache:indexPath defaultHeight:0] > 0) { return YES; } return NO; } // init cache -(void) initEstimatedRowHeightCacheIfNeeded { if (self.estimatedRowHeightCache == nil) { self.estimatedRowHeightCache = [[NSMutableDictionary alloc] init]; } } // custom [self.tableView reloadData] -(void) tableViewReloadData { // clear cache on reload self.estimatedRowHeightCache = [[NSMutableDictionary alloc] init]; [self.tableView reloadData]; } 

这个bug是由于没有tableView:estimatedHeightForRowAtIndexPath:方法造成的。 这是UITableViewDelegate协议的可选部分。

这不是它应该如何工作。 苹果的文档说:

提供估计行高度可以改善加载表视图时的用户体验。 如果表格包含可变高度的行,则计算所有高度可能是昂贵的,因此导致更长的加载时间。 使用估算function可以将几何计算的一些成本从加载时间推迟到滚动时间。

所以这个方法应该是可选的。 你会想如果你跳过它,它会回落在准确的tableView:heightForRowAtIndexPath:对不对? 但是,如果你在iOS 8上跳过它,你会得到这种行为。

什么似乎在发生? 我没有内部知识,但它看起来像是如果你没有实现这个方法,UITableView会把它作为估计的行高为0 。 它会弥补这一点(至less在某些情况下,在日志中抱怨),但你仍然会看到一个不正确的大小。 这显然是UITableView中的一个bug。 你可以在苹果的一些应用程序中看到这个bug,包括基本的设置。

那么你如何解决它? 提供方法! 实现tableView: estimatedHeightForRowAtIndexPath: 如果你没有更好的(快速)估计,只需返回UITableViewAutomaticDimension 。 这将完全解决这个错误。

喜欢这个:

 - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath { return UITableViewAutomaticDimension; } 

有潜在的副作用。 你提供了一个非常粗略的估计。 如果你看到这个结果(可能是你滚动的单元格大小),你可以尝试返回一个更准确的估计值。 (请记住, 估计 。)

也就是说,这种方法不应该返回一个完美的大小,只是一个足够好的大小。 速度比准确性更重要。 当我在模拟器中发现了一些滚动故障时,实际设备上没有任何应用程序,无论是iPhone还是iPad。 (我实际上已经尝试过写一个更准确的估计,但是很难平衡速度和准确性,而且在我的任何应用程序中根本没有可观察到的差异,它们的工作原理和刚刚返回的UITableViewAutomaticDimension ,这个更简单,修复这个bug。)

所以我build议你不要试图做更多,除非需要更多。 如果不需要,做更多的事情更可能导致错误,而不是修复它们。 在某些情况下,您最终可能会返回0 ,并且取决于何时返回它可能导致原始问题重新出现。

Kai上面的答案似乎起作用的原因是它实现了tableView:estimatedHeightForRowAtIndexPath:因此避免了0的假设。 当视图消失时它不返回0 。 也就是说,Kai的回答过于复杂,缓慢,并且不仅仅是返回UITableViewAutomaticDimension 。 (但是,再次感谢Kai。如果我没有看到你的答案,并且被启发把它分开,并弄清楚它为什么起作用,我从来没有想到这一点。

请注意,您可能还需要强制单元格的布局。 你会认为iOS会在你返回单元格的时候自动完成,但是并不总是这样。 (我会编辑一次,当我需要做更多的调查的时候,我会进行一些调查。)

如果您需要这样做,请在return cell;之前使用此代码return cell;

 [cell.contentView setNeedsLayout]; [cell.contentView layoutIfNeeded]; 

我有同样的问题。 表视图有几个不同的单元类,每个单元类都是不同的高度。 而且,其中一个单元类必须显示额外的文本,这意味着更多的变化。

在大多数情况下,滚动是完美的。 然而,问题中所描述的同样的问题performance出来。 也就是说,select了一个表格单元格,并提交了另一个视图控制器,返回到原始表格视图,向上滚动是非常生涩。

第一个调查的目的是要考虑为什么要重新加载数据。 经过实验,我可以确认,返回到表视图,数据重新加载,尽pipe不使用reloadData

看到我的评论ios 8 tableview自动重新加载时popup后视图出现

由于没有任何机制可以停用这种行为,因此接下来的方法是调查干扰滚动。

我得出的结论是由estimatedHeightForRowAtIndexPath的HeightForRowAtIndexPath返回的estimatedHeightForRowAtIndexPath是估计的预先计算。 logging到控制台的估计,你会看到委托方法查询每一行时,表视图第一次出现。 这是滚动之前。

我很快发现,我的代码中的一些高度估计逻辑是错误的。 解决这个问题解决了最糟糕的问题。

为了达到完美的滚动效果,我对上面的答案采取了一些不同的方法。 高度被caching,但是使用的值是从用户向下滚动时捕获的实际高度:

  var myRowHeightEstimateCache = [String:CGFloat]() 

储藏:

 func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) { myRowHeightEstimateCache["\(indexPath.row)"] = CGRectGetHeight(cell.frame) } 

从caching中使用:

 func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { if let height = myRowHeightEstimateCache["\(indexPath.row)"] { return height } else { // Not in cache ... try to figure out estimate } 

请注意,在上述方法中,您将需要返回一些估计,因为该方法当然会在didEndDisplayingCell之前didEndDisplayingCell

我的猜测是,所有这一切都有一些苹果的错误。 这就是为什么这个问题只能在退出场景中体现出来。

底线是,这个解决scheme是非常相似的以上。 但是,我避免了任何棘手的计算,并使用UITableViewAutomaticDimension行为来caching使用didEndDisplayingCell显示的实际行高。

TLDR:通过caching实际的行高来解决最有可能的UIKit缺陷。 然后查询您的caching作为估计方法中的第一个选项。

那么,直到它工作,你可以删除这两行:

 self.tableView.estimatedRowHeight = 45 self.tableView.rowHeight = UITableViewAutomaticDimension 

并将此方法添加到您的viewController:

 override func tableView(tableView: UITableView!, heightForRowAtIndexPath indexPath: NSIndexPath!) -> CGFloat { let cell = tableView.dequeueReusableCellWithIdentifier("cell") as TableViewCell cell.cellLabel.text = self.tableArray[indexPath.row] //Leading space to container margin constraint: 0, Trailling space to container margin constraint: 0 let width = tableView.frame.size.width - 0 let size = cell.cellLabel.sizeThatFits(CGSizeMake(width, CGFloat(FLT_MAX))) //Top space to container margin constraint: 0, Bottom space to container margin constraint: 0, cell line: 1 let height = size.height + 1 return (height <= 45) ? 45 : height } 

它在您的testing项目中没有任何其他更改。

如果你已经设置了tableView的estimatedRowHeight属性。

 tableView.estimatedRowHeight = 100; 

然后评论它。

 // tableView.estimatedRowHeight = 100; 

它解决了我在iOS8.1中发生的错误。

如果你真的想保留它,那么你可以在推送之前强制tableView重载数据。

 [self.tableView reloadData]; [self.navigationController pushViewController:vc animated:YES]; 

或者viewWillDisappear:viewWillDisappear:

 - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [self.tableView reloadData]; } 

希望能帮助到你。

在Xcode 6最后对我来说,解决方法不起作用。 我正在使用自定义单元格,并将heightForCell中的单元格取出导致无限循环。 当离队的时候,调用heightForCell。

而且这个错误似乎仍然存在。

如果以上都没有为你工作(正如它发生在我身上),只是从表视图检查estimatedRowHeight财产是准确的。 我检查了我使用的是50像素,实际上它接近150像素。 更新这个值解决了这个问题!

 tableView.rowHeight = UITableViewAutomaticDimension tableView.estimatedRowHeight = tableViewEstimatedRowHeight // This should be accurate.