【问题标题】:Jerky Scrolling After Updating UITableViewCell in place with UITableViewAutomaticDimension使用 UITableViewAutomaticDimension 更新 UITableViewCell 后的生涩滚动
【发布时间】:2015-03-15 19:04:02
【问题描述】:

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

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

初始提要将下载 3 个 cmets 以供预览,但如果有更多 cmets,或者如果用户添加或删除评论,我想通过添加或更新提要表视图内部的 PostCell将CommentCells 删除到PostCell 内的cmets 表中。我目前正在使用以下助手来完成此操作:

// (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 中添加或删除了 cmets 来更新帖子。我在 feed 表中使用自动调整大小 PostCells。 PostCell 的 cmets 表展开以显示所有 cmets,但动画有点生涩,在单元格更新动画发生时,表有点上下滚动十几个像素。

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

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

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

【问题讨论】:

    标签: ios uitableview swift ios8


    【解决方案1】:

    这是我找到的解决此类问题的最佳解决方案(滚动问题 + 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];
    }
    

    斯威夫特 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 Dictionary 而不是 NSMutableDictionary?顺便说一句,这个解决方案效果很好,谢谢!
    • @dosdos 上帝保佑你,伙计!
    • 对我来说还是不行,我有一个自定义的imageView,并根据宽高的维度来更新它的高度约束。即使缓存了单元格高度,滚动仍然在跳跃,尤其是在屏幕上与当前高度不同的新单元格滚动到屏幕之前。
    • 太棒了!精彩的!真的很好用!!!!!!太感谢了!我认为您可以将其标记为解决方案。
    【解决方案2】:

    我们遇到了同样的问题。它来自对单元格高度的错误估计,导致 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  
    }
    

    【讨论】:

    • 我最终实现了 heightForRowAtIndexPath 方法并缓存了结果以提高性能,因为它有点涉及并且提要可能很长。当用户在提要中的一个帖子上添加/删除或加载 cmets 时,我会使该单元格的高度计算无效,以便在滚动期间重新计算它。抖动消失了,我希望我可以简化代码并利用新的高度计算功能,但我无法让它与我的 TableViewCell 一起工作得足够好
    • 你试过我上面描述的方法了吗?这是在 iOS 8 上应该完成的方式,不应该计算高度,因为框架已经处理了它。如果您实现 heightForRowAtIndexPath 方法,您只是覆盖了 SDK 的行为。
    • @BryanAlger 是正确的。自动行高不适用于具有大量行且高度变化很大的表格。为了可靠的平滑滚动,您必须在 heightForRowAtIndexPath 方法中给出正确的结果,最好使用缓存的行高。否则,当 tableView 需要更新它的 contentSize 时,你会感到不快,尤其是在你推送或显示另一个视图控制器并返回的情况下。不幸的是,自动行高仅适用于具有几行的简单 tableView。
    • 在实现 heightForRowAtIndexPath 的情况下,您没有使用自动单元格高度尺寸的功能。当然,实现它会起作用,但是你的单元格不是动态的。
    • 就我而言,我已经在heightForRowAtIndexPath 中实现了单元格配置、高度计算和高度缓存,但仍然有一个生涩的UITableView 滚动。遵循@GabrielCartier 的回答并根据单元格类型添加更具体的逻辑确实帮助并解决了问题,谢谢!
    【解决方案3】:

    dosdos 回答在 Swift 2

    中为我工作

    声明 ivar

    var heightAtIndexPath = NSMutableDictionary()
    

    在函数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 = [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
    }
    

    【讨论】:

    • 太棒了,完全消除了闪烁。
    • 添加了 SWIFT 3 的更新(不要忘记 viewDidLoad 中的 self.tableView.rowHeight = UITableViewAutomaticDimension)
    • 不幸的是,这对我没有帮助。自动布局在这里有点奇怪。
    • @Ranknoodle.. 谢谢!这对我有帮助
    • 嘿,我编辑了您的答案以使用打字字典。我只是想粘贴我自己的 swift 4 代码作为答案,但发现编辑你的就足够了。希望你不介意。
    【解决方案4】:

    @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

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

    【讨论】:

      【解决方案5】:

      我也遇到了同样的问题。我确实找到了一种解决方法,但它并不能完全解决这个问题。不过比起之前的断断续续的滚动,似乎好多了。

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

      cell.setNeedsUpdateConstraints()
      cell.updateConstraintsIfNeeded()
      

      编辑:您可能还需要使用 tableView.estimatedRowHeight 值来获得更平滑的滚动。

      【讨论】:

      • 我不建议使用这种方法,在 cellForRowAtIndexPath 这样的方法中调用自动布局方法会极大地影响 TableView 的性能。
      【解决方案6】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-12-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多