在使用UITableViewAutomaticDimension更新UITableViewCell之后进行滚动

我正在构build一个具有用户提交的post的提要视图的应用程序。 这个视图有一个自定义UITableViewCell实现的UITableViewCell 。 在这个单元UITableView ,我有另外一个显示注释的UITableView 。 要点是这样的:

 Feed TableView PostCell Comments (TableView) CommentCell PostCell Comments (TableView) CommentCell CommentCell CommentCell CommentCell CommentCell 

最初的Feed将下载3个评论用于预览,但是如果有更多评论,或者如果用户添加或删除评论,我想通过添加或删除CommentCells到评论PostCell来更新PostCell中的PostCellPostCell里面的表。 我目前正在使用下面的帮手来完成这个工作:

 // (PostCell.swift) Handle showing/hiding comments func animateAddOrDeleteComments(startRow: Int, endRow: Int, operation: CellOperation) { let table = self.superview?.superview as UITableView // "table" is outer feed table // self is the PostCell that is updating it's comments // self.comments is UITableView for displaying comments inside of the PostCell table.beginUpdates() self.comments.beginUpdates() // This function handles inserting/removing/reloading a range of comments // so we build out an array of index paths for each row that needs updating var indexPaths = [NSIndexPath]() for var index = startRow; index <= endRow; index++ { indexPaths.append(NSIndexPath(forRow: index, inSection: 0)) } switch operation { case .INSERT: self.comments.insertRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None) case .DELETE: self.comments.deleteRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None) case .RELOAD: self.comments.reloadRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None) } self.comments.endUpdates() table.endUpdates() // trigger a call to updateConstraints so that we can update the height constraint // of the comments table to fit all of the comments self.setNeedsUpdateConstraints() } override func updateConstraints() { super.updateConstraints() self.commentsHeight.constant = self.comments.sizeThatFits(UILayoutFittingCompressedSize).height } 

这完成更新就好了。 如预期的那样,该PostCellPostCell内部添加或删除的注释被更新。 我正在Feed表中使用自动调整PostCells的大小。 PostCell的注释表扩展为显示所有的注释,但animation有点生涩,并且在单元更新animation发生时,上下滚动十几个像素。

在resize的过程中跳跃有点烦人,但是我的主要问题随之而来。 现在,如果我在Feed中向下滚动,滚动就像之前一样平滑,但是如果我在添加注释后resize的单元格上方滚动,则Feed会在向Feed顶部前几次向后跳跃。 我设置iOS8自动resize的单元格像这样:

 // (FeedController.swift) // tableView is the feed table containing PostCells self.tableView.rowHeight = UITableViewAutomaticDimension self.tableView.estimatedRowHeight = 560 

如果我删除estimatedRowHeight ,则只要单元格高度发生变化,表格就会滚动到顶部。 现在,我感觉现在已经很困难了,作为一个新的iOS开发者,可以使用你可能有的任何提示。

这里是我发现解决这种问题的最佳解决scheme(滚动问题+ reloadRows + iOS 8 UITableViewAutomaticDimension);

它包括保持每一个字典高度和更新(在字典中),因为tableView将显示单元格。

然后你将返回保存的高度- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath方法。

你应该实现这样的东西:

Objective-C的

 - (void)viewDidLoad { [super viewDidLoad]; self.heightAtIndexPath = [NSMutableDictionary new]; self.tableView.rowHeight = UITableViewAutomaticDimension; } - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath { NSNumber *height = [self.heightAtIndexPath objectForKey:indexPath]; if(height) { return height.floatValue; } else { return UITableViewAutomaticDimension; } } - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { NSNumber *height = @(cell.frame.size.height); [self.heightAtIndexPath setObject:height forKey:indexPath]; } 

Swift 3

 @IBOutlet var tableView : UITableView? var heightAtIndexPath = NSMutableDictionary() override func viewDidLoad() { super.viewDidLoad() tableView?.rowHeight = UITableViewAutomaticDimension } func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { if let height = heightAtIndexPath.object(forKey: indexPath) as? NSNumber { return CGFloat(height.floatValue) } else { return UITableViewAutomaticDimension } } func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { let height = NSNumber(value: Float(cell.frame.size.height)) heightAtIndexPath.setObject(height, forKey: indexPath as NSCopying) } 

我们有同样的问题。 它来自于对单元格高度的错误估计,导致SDK强制高度不好,这将导致滚动时单元格的跳跃。 根据你如何构build你的单元格,解决这个问题的最好方法是实现UITableViewDelegate方法- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath

只要你的估计非常接近细胞高度的实际值,这几乎可以消除跳跃和急动。 下面是我们如何实现它,你会得到的逻辑:

 - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath { // This method will get your cell identifier based on your data NSString *cellType = [self reuseIdentifierForIndexPath:indexPath]; if ([cellType isEqualToString:kFirstCellIdentifier]) return kFirstCellHeight; else if ([cellType isEqualToString:kSecondCellIdentifier]) return kSecondCellHeight; else if ([cellType isEqualToString:kThirdCellIdentifier]) return kThirdCellHeight; else { return UITableViewAutomaticDimension; } } 

增加了Swift 2支持

 func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { // This method will get your cell identifier based on your data let cellType = reuseIdentifierForIndexPath(indexPath) if cellType == kFirstCellIdentifier return kFirstCellHeight else if cellType == kSecondCellIdentifier return kSecondCellHeight else if cellType == kThirdCellIdentifier return kThirdCellHeight else return UITableViewAutomaticDimension } 

dosdos答案在Swift 2中为我工作

宣布伊娃

 var heightAtIndexPath = NSMutableDictionary() 

在func viewDidLoad()

 func viewDidLoad() { .... your code self.tableView.rowHeight = UITableViewAutomaticDimension } 

然后添加以下2个方法:

 override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { let height = self.heightAtIndexPath.objectForKey(indexPath) if ((height) != nil) { return CGFloat(height!.floatValue) } else { return UITableViewAutomaticDimension } } override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) { let height = cell.frame.size.height self.heightAtIndexPath.setObject(height, forKey: indexPath) } 

SWIFT 3:

 var heightAtIndexPath = NSMutableDictionary() func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { let height = self.heightAtIndexPath.object(forKey: indexPath) if ((height) != nil) { return CGFloat(height as! CGFloat) } else { return UITableViewAutomaticDimension } } func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { let height = cell.frame.size.height self.heightAtIndexPath.setObject(height, forKey: indexPath as NSCopying) } 

我也面临同样的问题。 我find了一个解决方法,但并没有完全解决这个问题。 但是与之前的波涛汹涌的滚动相比,它似乎要好很多。

在你的UITableView委托方法中:cellForRowAtIndexPath:在返回单元格之前尝试使用以下两种方法来更新约束。 (Swift语言)

 cell.setNeedsUpdateConstraints() cell.updateConstraintsIfNeeded() 

编辑:你可能也必须玩tableView.estimatedRowHeight值来获得更平滑的滚动。

在@dosdos之后回答。

我也发现有趣的实现: tableView(tableView: didEndDisplayingCell: forRowAtIndexPath:

特别是对于我的代码,其中单元格正在dynamic地更改约束,而单元格已经显示在屏幕上。 像这样更新字典有助于第二次显示单元格。

 var heightAtIndexPath = [NSIndexPath : NSNumber]() .... tableView.rowHeight = UITableViewAutomaticDimension tableView.estimatedRowHeight = UITableViewAutomaticDimension .... extension TableViewViewController: UITableViewDelegate { //MARK: - UITableViewDelegate func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { let height = heightAtIndexPath[indexPath] if let height = height { return CGFloat(height) } else { return UITableViewAutomaticDimension } } func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) { let height: NSNumber = CGRectGetHeight(cell.frame) heightAtIndexPath[indexPath] = height } func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) { let height: NSNumber = CGRectGetHeight(cell.frame) heightAtIndexPath[indexPath] = height } } 

@dosdos解决scheme工作正常

但是你应该添加一些东西

以下@dosdos的答案

Swift 3/4

 @IBOutlet var tableView : UITableView! var heightAtIndexPath = NSMutableDictionary() override func viewDidLoad() { super.viewDidLoad() tableView?.rowHeight = UITableViewAutomaticDimension } func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { if let height = heightAtIndexPath.object(forKey: indexPath) as? NSNumber { return CGFloat(height.floatValue) } else { return UITableViewAutomaticDimension } } func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { let height = NSNumber(value: Float(cell.frame.size.height)) heightAtIndexPath.setObject(height, forKey: indexPath as NSCopying) } 

然后使用这条线,当你想要的,对我来说,我用它在textDidChange内

  1. 首先重新加载Tableview
  2. 更新约束
  3. 最后移到桌面顶端

     tableView.reloadData() self.tableView.layoutIfNeeded() self.tableView.setContentOffset(CGPoint.zero, animated: true)