如何在UITableView分页加载时显示Shimmer(骨架屏)效果?
实现分页加载时显示Shimmer效果的方案
1. 扩展状态枚举,区分初始加载与分页加载
当前的State枚举无法区分初始加载和分页加载,我们需要新增一个状态来标记分页加载中:
enum State { case loading // 初始加载 case loadingMore // 分页加载 case loaded case error }
2. 修改分页触发逻辑,触发时更新状态并刷新列表
在滚动触发分页时,先标记分页加载状态并刷新列表,再请求数据:
extension RecipientsRootVC: UIScrollViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { guard scrollView != recentCollectionView else { return } let contentOffsetY = scrollView.contentOffset.y let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height let threshold: CGFloat = 10.0 if maximumOffset - contentOffsetY <= threshold { guard !loadingData else { return } loadingData = true state = .loadingMore // 刷新列表以显示底部Shimmer(也可用insertRows实现局部刷新,更流畅) tableView.reloadData() length += 10 viewModel?.loadMoreRecipients(length: length) print("Paging called \(length)") } } }
3. 更新TableView数据源逻辑,处理分页加载状态
调整行数计算和单元格创建逻辑,在分页加载时为列表底部添加Shimmer单元格:
extension RecipientsRootVC: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { viewModel?.numberOfSection() ?? 0 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { let realRowCount = (viewModel?.numberofRow(in: section)).safe switch state { case .loading: return 5 case .loadingMore: return realRowCount + 1 // 新增一行用于显示Shimmer case .loaded, .error: return realRowCount } } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let realRowCount = (viewModel?.numberofRow(in: indexPath.section)).safe switch state { case .loading: let cell = tableView.dequeueReusableCell(withIdentifier: "RecipientShimmerCell", for: indexPath) as! RecipientShimmerCell return cell case .loadingMore: // 最后一行显示Shimmer,其余行显示真实数据 if indexPath.row == realRowCount { let cell = tableView.dequeueReusableCell(withIdentifier: "RecipientShimmerCell", for: indexPath) as! RecipientShimmerCell return cell } else { guard let model = viewModel?.list?[indexPath.row] else { return UITableViewCell() } let cell: RecipientCell = tableView.dequeCell(RecipientCell.self, for: indexPath) cell.configure(data: RecipientCellVM(model: model)) cell.selectionStyle = .none cell.fullCellClicked = { self.viewModel?.getRecipientDetails(model.id.safe) } cell.onRightArrowClicked = { self.viewModel?.getRecipientDetails(model.id.safe) } return cell } case .loaded, .error: guard let model = viewModel?.list?[indexPath.row] else { return UITableViewCell() } let cell: RecipientCell = tableView.dequeCell(RecipientCell.self, for: indexPath) cell.configure(data: RecipientCellVM(model: model)) cell.selectionStyle = .none cell.fullCellClicked = { self.viewModel?.getRecipientDetails(model.id.safe) } cell.onRightArrowClicked = { self.viewModel?.getRecipientDetails(model.id.safe) } return cell } } }
4. 完善加载完成后的状态重置
数据加载完成后,重置状态和加载标记,刷新列表移除Shimmer:
extension RecipientsRootVC: RecipientsViewModelDelegate { func dataLoadingStarted() { print("Data loading started.") } func dataLoadingCompleted(success: Bool) { state = success ? .loaded : .error loadingData = false tableView.reloadData() print(success ? "Data loading completed successfully." : "Data loading failed.") } }
可选优化:局部刷新提升流畅度
如果不想每次分页都刷新整个列表,可以将scrollViewDidScroll中的tableView.reloadData()替换为局部插入行的逻辑:
// 假设只有一个section,多section需对应调整 let indexPath = IndexPath(row: realRowCount, section: 0) tableView.insertRows(at: [indexPath], with: .automatic)
内容的提问来源于stack exchange,提问作者Rashed
相关产品推荐
相关产品推荐

