You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 19:57:34