使用UITableViewAutomaticDimension更新UITableViewCell后的生涩滚动

Bry*_*ger 78 uitableview ios swift ios8

我正在构建一个具有用户提交帖子的Feed视图的应用.此视图具有UITableView自定义UITableViewCell实现.在这个单元格中,我有另一个UITableView用于显示注释.要点是这样的:

Feed TableView
  PostCell
    Comments (TableView)
      CommentCell
  PostCell
    Comments (TableView)
      CommentCell
      CommentCell
      CommentCell
      CommentCell
      CommentCell
Run Code Online (Sandbox Code Playgroud)

初始订阅源将下载3条评论进行预览,但如果有更多评论,或者用户添加或删除评论,我想PostCell通过添加或删除CommentCells评论表内部来更新Feed表视图内的位置的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
}
Run Code Online (Sandbox Code Playgroud)

这样就完成了更新.帖子已更新到位,并PostCell在预期内添加或删除了评论.我PostCells在feed表中使用自动调整大小.PostCell扩展的注释表显示所有注释,但动画有点不稳定,并且在进行单元格更新动画时,表格会向上和向下滚动十几个像素.

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

// (FeedController.swift)
// tableView is the feed table containing PostCells
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 560
Run Code Online (Sandbox Code Playgroud)

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

dos*_*dos 119

这是我发现解决此类问题的最佳解决方案(滚动问题+ 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];
}
Run Code Online (Sandbox Code Playgroud)

斯威夫特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)
}
Run Code Online (Sandbox Code Playgroud)

  • 对我来说,它仍然不起作用,我有一个自定义的imageView,并根据宽度和高度的尺寸来更新其高度约束.即使缓存了单元格高度,滚动仍然会跳跃,特别是在具有与屏幕上当前高度不同的新单元格滚动到屏幕之前. (5认同)
  • @dosdos上帝保佑你,老兄! (3认同)
  • 对我来说这是一个更好的解决方 它完美地运作 (2认同)
  • 真正简单有效的解决方案.谢谢! (2认同)

Gab*_*ier 30

我们遇到了同样的问题.它来自对单元格高度的错误估计,导致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;
    }
}
Run Code Online (Sandbox Code Playgroud)

添加了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  
}
Run Code Online (Sandbox Code Playgroud)

  • @BryanAlger是对的.自动行高度不适用于具有大量高度差异的行的表.要获得可靠的平滑滚动,必须在heightForRowAtIndexPath方法中提供正确的结果,最好使用缓存的行高.否则,当tableView需要更新它的contentSize时,你会变得急躁,特别是在推送或显示另一个视图控制器并返回的情况下.不幸的是,自动行高仅适用于具有几行的简单tableView. (7认同)
  • 你尝试过我上面描述的方法吗?这是应该使用iOS 8的方式,不应该计算高度,因为框架已经处理它.如果实现heightForRowAtIndexPath方法,则只是覆盖SDK的行为. (2认同)
  • 在您实现heightForRowAtIndexPath的情况下,您没有使用自动单元格高度维度的功能.当然,实现它会起作用,但是你的细胞不是动态的. (2认同)

Ran*_*dle 21

dosdos的答案在Swift 2中为我工作

宣布伊娃

var heightAtIndexPath = NSMutableDictionary()
Run Code Online (Sandbox Code Playgroud)

在func viewDidLoad()中

func viewDidLoad() {
  .... your code
  self.tableView.rowHeight = UITableViewAutomaticDimension
}
Run Code Online (Sandbox Code Playgroud)

然后添加以下两种方法:

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)
}
Run Code Online (Sandbox Code Playgroud)

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
}
Run Code Online (Sandbox Code Playgroud)