使用 UITableViewAutomatic維度更新 UITableViewCell 之后的跳动滚动

我正在建立一个应用程序,有一个用户提交的文章饲料视图。这个视图有一个具有自定义 UITableViewCell实现的 UITableView。在这个单元格中,我有另一个用于显示注释的 UITableView。大意是这样的:

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

最初的提要会下载3条评论以供预览,但是如果有更多的评论,或者用户添加或删除了一条评论,我想通过添加或删除 CommentCellsPostCell内部的评论表来更新提要表视图内部的 PostCell。我目前使用以下助手来完成这项工作:

// (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
}

这样可以很好地完成更新。根据预期,在 PostCell内部添加或删除评论,更新帖子。我正在饲料表中使用自动调整大小的 PostCellsPostCell的注释表展开以显示所有的注释,但是动画有点不稳定,当单元格更新动画时,表格会上下滚动大约12个像素。

调整尺寸时的跳跃有点烦人,但是我的主要问题出现在调整尺寸之后。现在,如果我在 feed 中向下滚动,滚动依然平滑,但是如果我在单元格上方向上滚动,我刚刚在添加注释之后调整了大小,feed 会在到达 feed 顶部之前向后跳动几次。我为 Feed 设置了 iOS8自动调整单元格,如下所示:

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

如果删除 estimatedRowHeight,那么每当单元格高度发生变化时,表就会滚动到顶部。作为一个新的 iOS 开发者,我现在感觉自己被这个问题困住了,可能需要你提供的任何建议。

29142 次浏览

我们也有同样的问题。它来自于对单元格高度的错误估计,导致 SDK 强制执行错误的高度,从而在向上滚动时导致单元格跳转。根据构建单元的方式,解决这个问题的最佳方法是实现 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
}

我也面临着同样的问题。我确实找到了一个变通方法,但它不能完全修复混蛋。但它似乎比以前的波涛汹涌的滚动要好得多。

UITableView委托方法 :cellForRowAtIndexPath:中,尝试使用以下两种方法在返回单元格之前更新约束。(斯威夫特语)

cell.setNeedsUpdateConstraints()
cell.updateConstraintsIfNeeded()

编辑: 您可能还必须调整 tableView.estimatedRowHeight值以获得更平滑的滚动。

下面是我找到的解决这类问题的最佳方案(滚动问题 + reloadRows + iOS8 UITableViewAutomatic維度) ;

它包括在字典中保存每个高度,并在 tableView 显示单元格时更新它们(在字典中)。

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

你应该实现这样的东西:

目标 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)
}

我在 Swift 2的时候就是这么回答的

宣布静脉注射

var heightAtIndexPath = NSMutableDictionary()

在 func viewDidLoad ()中

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

然后添加以下两种方法:

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 = [IndexPath: CGFloat]()


func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return self.heightAtIndexPath[indexPath] ?? UITableViewAutomaticDimension
}


func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
self.heightAtIndexPath[indexPath] = cell.frame.size.height
}

按照 @ dosdos的回答。

我还发现了实现的有趣之处: tableView(tableView: didEndDisplayingCell: forRowAtIndexPath:

特别是对于我的代码,当单元格已经显示在屏幕上时,单元格正在动态更改约束。像这样更新 Dictionary 有助于第二次显示单元格。

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 解决方案正常

但是有些东西你应该补充一下

以下是@dosdos 的回答

斯威夫特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)