我使用自定义TableView分页的想法,使用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方法也是可以重现的。
我在使用UITableView.automaticDimension
表示单元格高度时遇到了这个问题。我使用更高的属性值解决了这个问题estimatedRowHeight
。
像这样:
tableView.estimatedRowHeight = 1000
现在,将仅对可见的行调用willDisplay
。
正如@iWheeBuy所说,willDisplayCell并没有说单元格在屏幕上,所以你可以通过签入tableView.visibleCells来做到这一点。
但是,即使您在滚动时调用此函数并且它将出现在屏幕上tableView.visibleCells.contains(cell)
也会是假的。
一种对我有用的方法是延迟,因此完整代码是:(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
}
}
}
来自有关tableView(_:willDisplay:forRowAt:)
的文档:
表视图在使用之前将此消息发送给其委托 单元格以绘制一行,从而允许委托自定义 单元格对象,然后显示它。此方法为委托提供 有机会覆盖表之前设置的基于状态的属性 视图,例如选择和背景颜色。委托人之后 返回,表视图仅设置 Alpha 和帧属性,并且 然后仅在行滑入或滑出时对其进行动画处理时。
此描述未提及将仅对屏幕上的单元格调用该方法。
您可以将分页逻辑放在其他一些方法/方法中。例如:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
//
}
并使用例如scrollView.contentOffset
、scrollView.contentSize
、tableView.indexPathsForVisibleRows
等检查表的状态...
tableView(_:willDisplay:forRowAt:): 调用此方法时,无论所有单元格是否可见,都将首次返回所有单元格。 因此,如果要在此方法中实现分页逻辑,则必须首先检查可见单元格。 即
if let visibleRows = tableView.indexPathsForVisibleRows , indexPath == visibleRows.last {
guard isLoadingIndexPath(indexPath) else { return }
if self.totalItems > orders.count {
fetchNextPage()
}
}
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)
}
}
}
}
}
实现这 3 个 scrollViewDelegate 方法,并使用固定高度的单元格编号和当前 scrollOffset 来了解它是否是最后一个单元格
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
}
//Pagination
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
}
事实上,UICollectionView
上有适合您需求的属性prefetchingEnabled
。
- 预取已启用
总结
表示是否启用了单元格和数据预取。声明
@property(nonatomic, getter=isPrefetchingEnabled) BOOL prefetchingEnabled;
讨论
如果为 YES,则集合视图会在单元格请求之前请求单元格 将显示,将渲染分布在多个布局上 通过。如果为 NO,则根据需要请求单元格 显示,通常在同一渲染中请求多个单元格 圈。将此属性设置为 NO 还会禁用数据预取。这 此属性的默认值为 YES。注意 当预取 启用了集合视图:单元格项目在索引路径:方法上 集合视图委托在单元格 必填。若要避免视觉外观不一致,请使用 集合视图:将显示单元格:forItemAtIndexPath: 委托方法 更新单元格以反映视觉状态,例如选择。
源: 苹果文档
在界面构建器中选择UITableView。打开尺寸检查器,然后在"表格视图"部分取消选中"估算"行旁边的复选框。