【问题标题】:TableView willDisplayCell called for cells that are not on the screen (tableView pagination)TableView willDisplayCell 为不在屏幕上的单元格调用(tableView 分页)
【发布时间】:2018-06-16 14:53:48
【问题描述】:

我使用the idea of custom TableView pagination 使用 willDisplayCell(或 cellForRowAt)并从服务器更新。

问题是即使对于不在屏幕上的单元格也会调用 willDisplay。

如何处理这种行为并更改流程以仅在用户滚动到最后一个单元格时更新单元格?

private func isLoadingIndexPath(_ indexPath: IndexPath) -> Bool {
    return indexPath.row == self.orders.count - 1
}

override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    // fetch new data if user scroll to the last cell
    guard isLoadingIndexPath(indexPath) else { return }
    if self.totalItems > orders.count {
        fetchNextPage()
    }
}

private func fetchNextPage() {
    self.currentPage += 1
    self.delegate?.didReachLastCell(page: currentPage)
}

didReachLastCell 调用 addOrders:

func addOrders(_ orders: [OrderView]) {
    self.orders.append(contentsOf: orders)
    reloadOrders()
}

fileprivate func reloadOrders() {
    self.tableView.reloadData()
}

注意:同样的问题对于 cellForRowAt 方法也可以重现。

【问题讨论】:

    标签: ios swift uitableview pagination


    【解决方案1】:

    实现这 3 个 scrollViewDelegate 方法,并使用您的固定高度单元格编号和当前 scrollOffset 来了解它是否是最后一个单元格

    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
    
    }
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    
    }
    //Pagination
    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    
     }
    

    【讨论】:

    • 不,我有大约 13 个项目,但屏幕上只有 5 个。
    • 你使用固定单元格高度还是动态?
    • 我的身高是固定的。
    • 看看这是解决方案stackoverflow.com/a/41395441/5820010
    【解决方案2】:

    来自关于tableView(_:willDisplay:forRowAt:)的文档:

    一个表视图在它使用之前将这个消息发送给它的委托 单元格绘制一行,从而允许委托自定义 单元格对象在显示之前。此方法为委托提供了一个 有机会覆盖表之前设置的基于状态的属性 视图,例如选择和背景颜色。委托后 返回,表格视图仅设置 alpha 和 frame 属性,并且 然后仅在行滑入或滑出时为行设置动画。

    这个描述没有提到该方法只会被屏幕上的单元格调用。

    您可以将分页逻辑放到其他一些方法中。例如:

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        //
    }
    

    并使用例如scrollView.contentOffsetscrollView.contentSizetableView.indexPathsForVisibleRows 等检查表的状态...

    【讨论】:

    • 小心,scrollViewDidScroll 被调用的频率很高,不要在那里放沉重的逻辑。
    • 是的,我知道这一点。谢谢。但很奇怪,为什么 cellForRow/willDisplayCell 的解决方案适用于这个问题的人? stackoverflow.com/a/35475282/5969121
    • @atereshkov 当通用解决方案在某些特定情况下不起作用时,我有很多示例。我自己使用tableView(_:willDisplay:forRowAt:)。但是,如果为某些屏幕外 indexPaths 调用此方法,这对我的逻辑来说是可以的。只需搜索最适合您的解决方案
    • @atereshkov 我发现如果您将表格行高保持为自动尺寸,那么即使对于低于可见范围的第 20 个单元格,也会调用此 willDisplay。但是,如果您将表格行高保持为某个恒定值(比如说 50),那么您的分页逻辑将起作用,并且 willDisplayCell 只会在单元格即将从底部显示时被调用。
    • @iWheelBuy 描述很好,但奇怪的是,如果您正在制作一个包含 20 个单元格的表格,并且一次只有 3 或 4 个单元格可见,但您仍然是第一次收到调用willDisplayCell 中的第 20 个单元格也是如此。在我上面的评论中,我描述了自动尺寸问题。 willDisplayCell 就像一个委托,它告诉将要显示一个单元格。但我个人经历了第 20 个单元格显示调用,即使它远低于可见范围,将行高保持为自动。
    【解决方案3】:

    正如@iWheeBuy 所说,willDisplayCell 不会说单元格在屏幕上,因此您可以通过检查 tableView.visibleCells 来做到这一点。

    但是,即使在滚动时调用此函数并且它将出现在屏幕上,tableView.visibleCells.contains(cell) 也会为 false。

    对我有用的一种方法是延迟,所以完整的代码是:(SWIFT 4)

        func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                if tableView.visibleCells.contains(cell) {
                    // load more data
                }
            }
        }
    

    【讨论】:

    • 如果您尝试使用需要在屏幕上出现的所有单元格上调用的逻辑,我会小心使用此方法。单元格只会被绘制一次,这意味着如果单元格在可见之前被绘制,您的逻辑将永远被调用。
    【解决方案4】:

    我在使用UITableView.automaticDimension 作为单元格高度时遇到了这个问题。我使用更高的estimatedRowHeight 属性值解决了这个问题。

    类似这样的:

    tableView.estimatedRowHeight = 1000
    

    现在willDisplay 将只对可见的行调用。

    【讨论】:

    • dayum。这实际上是准确的,我不会想到它。我把它设置为 1。谢谢,伙计
    • 或者,如果所有单元格的高度都已知,(在我的例子中是 150)在此处设置该值,它也将正常工作。 :)
    • 我的情况是 50,改成 100 就可以了:D 谢谢兄弟
    • 这是个好建议!我通过将其值设置为 0 来禁用估计高度。
    【解决方案5】:

    事实上,UICollectionView 上的 prefetchingEnabled 属性可以满足您的需求。

    • prefetchingEnabled

    总结

    表示是否启用单元格和数据预取。声明

    @property(nonatomic, getter=isPrefetchingEnabled) BOOL prefetchingEnabled;
    

    讨论

    如果是,集合视图会提前请求单元格 将被显示,将渲染分布在多个布局上 通过。否时,请求单元格,因为它们需要 显示,通常在同一个渲染中请求多个单元格 环形。将此属性设置为 NO 也会禁用数据预取。这 此属性的默认值为 YES。注意 当预取是 启用 collectionView:cellForItemAtIndexPath: 方法 集合视图委托在单元格被调用之前被调用 必需的。为避免视觉外观不一致,请使用 collectionView:willDisplayCell:forItemAtIndexPath: 委托方法 更新单元格以反映视觉状态,例如选择。

    来源: Apple Documentation

    【讨论】:

      【解决方案6】:
      DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
         if tableView.visibleCells.contains(cell) {
            if (indexPath.row == self.arrProduct.count - 1){
                          if self.arrProduct.count > 0 {
                              if(self.arrProduct.count % self.RECORD_SET_LIMIT == 0 && self.isLoadMore) {
                                  self.currentPage = self.currentPage + 1
                                  print(self.currentPage)
                                  self.perform(#selector(self.serviceGetProductByStatus), with: nil, afterDelay: 0.1)
                              }
                          }
                      }
                  }
              }
      

      【讨论】:

        【解决方案7】:

        在您的界面构建器中选择 UITableView。打开尺寸检查器并在“表格视图”部分取消选中“估计”行旁边的复选框。

        【讨论】:

          【解决方案8】:

          tableView(_:willDisplay:forRowAt:): 调用此方法时,第一次返回所有单元格,无论所有单元格是否可见。 因此,如果您想在此方法中实现分页逻辑,则必须首先检查可见单元格。 即

              if let visibleRows = tableView.indexPathsForVisibleRows , indexPath == visibleRows.last {
                  guard isLoadingIndexPath(indexPath) else { return }
                  if self.totalItems > orders.count {
                      fetchNextPage()
                  }
              }
                
          

          【讨论】:

          • 这不是真的!这不是必需的,因为问题可能与估计的行高有关。使用正确的实现,所有单元格都不应在第一次可见。
          猜你喜欢
          • 2016-01-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-04-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多