ios 使用 UITableViewAutomaticDimension 将 UITableViewCell 更新到位后生涩滚动

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27996438/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 04:23:21  来源:igfitidea点击:

Jerky Scrolling After Updating UITableViewCell in place with UITableViewAutomaticDimension

iosuitableviewswiftios8

提问by Bryan Alger

I am building an app that has a feed view for user-submitted posts. This view has a UITableViewwith a custom UITableViewCellimplementation. Inside this cell, I have another UITableViewfor displaying comments. The gist is something like this:

我正在构建一个应用程序,该应用程序具有用户提交的帖子的提要视图。此视图具有UITableView自定义UITableViewCell实现。在这个单元格内,我有另一个UITableView用于显示评论。要点是这样的:

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

The initial feed will download with 3 comments for previewing, but if there are more comments, or if the user adds or deletes a comment, I want to update the PostCellin place inside of the feed table view by adding or removing CommentCellsto the comments table inside of the PostCell. I am currently using the following helper to accomplish that:

初始提要将下载 3 条评论以供预览,但如果有更多评论,或者用户添加或删除评论,我想PostCell通过添加或删除CommentCells评论表来更新提要表视图中的就地的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
}

This accomplishes the update just fine. The post is updated in place with comments added or removed inside of the PostCellas expected. I am using auto sizing PostCellsin the feed table. The comments table of the PostCellexpands to show all of the comments, but the animation is a bit jerky and the table sort of scrolls up and down a dozen pixels or so while the cell update animation takes place.

这样就可以很好地完成更新。帖子更新到位,PostCell并按预期在内部添加或删除评论。我PostCells在提要表中使用自动调整大小。的评论表PostCell展开以显示所有评论,但动画有点生涩,表格在单元格更新动画发生时上下滚动十几个像素左右。

The jumping during resizing is a bit annoying, but my main issue comes afterwards. Now if I scroll down in the feed, the scrolling is smooth as before, but if I scroll up above the cell I just resized after adding comments, the feed will jump backwards a few times before it reaches the top of the feed. I setup iOS8auto sizing cells for the Feed like this:

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

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

If I remove the estimatedRowHeight, the table just scrolls to the top anytime a cell height changes. I'm feeling pretty stuck on this now and as a new iOS developer, could use any tips you might have.

如果我删除estimatedRowHeight,表格只会在单元格高度发生变化时滚动到顶部。我现在对此感到非常困惑,作为一名新的 iOS 开发人员,可以使用您可能拥有的任何技巧。

回答by dosdos

Here is the best solution I found to solve this kind of problem (scrolling problem + reloadRows + iOS 8 UITableViewAutomaticDimension);

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

It consists by keeping every heights in a dictionary and updating them (in the dictionary) as the tableView will display the cell.

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

You will then return the saved height in - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPathmethod.

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

You should implement something like this :

你应该实现这样的东西:

Objective-C

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

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

回答by Gabriel Cartier

We had the same problem. It comes from a bad estimation of the cell height that causes the SDK to force a bad height which will cause the jumping of cells when scrolling back up. Depending on how you built your cell, the best way to fix this is to implement the UITableViewDelegatemethod - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath

我们遇到了同样的问题。它来自对单元格高度的错误估计,导致 SDK 强制设置错误的高度,这将导致向上滚动时单元格的跳跃。根据您构建单元的方式,解决此问题的最佳方法是实现该UITableViewDelegate方法- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath

As long as your estimation is pretty close to the real value of the cell height, this will almost cancel the jumping and jerkiness. Here's how we implemented it, you'll get the logic:

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

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

Added Swift 2 support

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

回答by Ranknoodle

dosdos answer worked for me in Swift 2

dosdos 答案在Swift 2 中对我有用

Declare the ivar

声明 ivar

var heightAtIndexPath = NSMutableDictionary()

in func viewDidLoad()

在函数 viewDidLoad() 中

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

Then add the following 2 methods:

然后添加以下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:

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

回答by Vishal Chandran

I was facing the same problem too. I did find a workaround, but it doesn't completely fix the jerk. But it seems to be a lot better compared to the previous choppy scrolling.

我也面临同样的问题。我确实找到了一种解决方法,但它并没有完全解决这个问题。但与之前的断断续续滚动相比,它似乎要好得多。

In your UITableViewdelegate method :cellForRowAtIndexPath:, try using the following two methods to update the constraints before returning the cell. (Swift language)

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

cell.setNeedsUpdateConstraints()
cell.updateConstraintsIfNeeded()

EDIT:You may also have to play around with the tableView.estimatedRowHeightvalue to get a smoother scrolling.

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

回答by Basil

@dosdos solution is working fine

@dosdos 解决方案工作正常

but there is something you should added

但你应该添加一些东西

following @dosdos answer

关注@dosdos 回答

Swift 3/4

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

then use this lines when ever you want , for me I use it inside textDidChange

然后在需要时使用此行,对我而言,我在textDidChange 中使用它

  1. first reload Tableview
  2. update constraint
  3. finally move to top Tableview

    tableView.reloadData()
    self.tableView.layoutIfNeeded()
    self.tableView.setContentOffset(CGPoint.zero, animated: true)
    
  1. 首先重新加载Tableview
  2. 更新约束
  3. 最后移到顶部 Tableview

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

回答by Gabriel.Massana

Following @dosdosanswer.

@dosdos回答。

I also found interesting to implement: tableView(tableView: didEndDisplayingCell: forRowAtIndexPath:

我还发现实施很有趣: tableView(tableView: didEndDisplayingCell: forRowAtIndexPath:

Specially for my code, where the cell is changing Constraints dynamically while the cell is already displayed on screen. Updating the Dictionary like this helps the second time the cell is displayed.

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

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