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

时间:2015-01-17 05:22:21

标签: ios uitableview swift ios8

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

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

初始Feed将下载3条评论进行预览,但如果有更多评论,或者用户添加或删除评论,我想更新Feed表视图中的PostCellCommentCells内的评论表中添加或删除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内添加或删除评论的位置进行了更新。我在Feed表中使用自动调整大小PostCellsPostCell的注释表展开以显示所有注释,但动画有点不稳定,并且在进行单元格更新动画时,表格会向上和向下滚动十几个像素。

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

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

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

6 个答案:

答案 0 :(得分:115)

这是我发现解决此类问题的最佳解决方案(滚动问题+ reloadRows + iOS 8 UITableViewAutomaticDimension);

它包括将每个高度保持在字典中并更新它们(在字典中),因为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)
}

答案 1 :(得分: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;
    }
}

添加了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  
}

答案 2 :(得分:21)

dosdos回答在 Swift 2

中为我工作

宣布ivar

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
}

答案 3 :(得分:2)

我也遇到了同样的问题。我确实找到了解决方法,但它并没有完全修复这个混蛋。但与之前不稳定的滚动相比,它似乎要好得多。

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

cell.setNeedsUpdateConstraints()
cell.updateConstraintsIfNeeded()

编辑:您可能还需要使用tableView.estimatedRowHeight值来进行更顺畅的滚动。

答案 4 :(得分:1)

关注@dosdos

我也觉得有趣的是:tableView(tableView: didEndDisplayingCell: forRowAtIndexPath:

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

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

答案 5 :(得分:0)

@dosdos解决方案工作正常

但是你应该添加一些东西

关注@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

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