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

使用RxSwift实现UITableView滚动到底部时加载更多数据

当然可以!RxSwift的响应式特性刚好能让UITableView的懒加载逻辑变得简洁又好维护,我给你拆解下具体的实现思路和代码:

核心实现思路

通过RxSwift监听UITableView的滚动事件,判断用户是否滚动到最后一个单元格,触发下一页数据请求;同时通过状态变量控制避免重复请求,并将新数据合并到现有数据源中,最终绑定到表格展示。

具体实现步骤

1. 准备核心变量

先定义几个关键的响应式变量,用来管理页码、数据源、加载状态和是否还有更多数据:

import RxSwift
import RxCocoa

class YourViewController: UIViewController {
    private let disposeBag = DisposeBag()
    private let currentPage = BehaviorRelay(value: 1) // 当前页码
    private let pageSize = 20 // 每页数据量
    private let dataSource = BehaviorSubject<[YourDataModel]>(value: []) // 数据源
    private let isLoading = BehaviorRelay(value: false) // 是否正在加载
    private let hasMoreData = BehaviorRelay(value: true) // 是否还有更多数据
    @IBOutlet weak var tableView: UITableView!
    
    // ... 其他代码
}

2. 监听表格滚动,触发加载

利用rx.willDisplayCell事件,当用户滚动到最后一个单元格时,判断状态后触发下一页请求:

tableView.rx.willDisplayCell
    .subscribe(onNext: { [weak self] cell, indexPath in
        guard let self = self else { return }
        // 获取当前数据源总数量
        guard let totalItems = try? self.dataSource.value().count else { return }
        // 满足条件:是最后一个cell、不在加载中、还有更多数据
        guard indexPath.row == totalItems - 1, 
              !self.isLoading.value, 
              self.hasMoreData.value else {
            return
        }
        // 页码+1,触发下一页请求
        self.currentPage.accept(self.currentPage.value + 1)
    })
    .disposed(by: disposeBag)

3. 绑定页码到API请求,合并数据源

监听页码变化,发起对应页的API请求,处理加载状态并合并新旧数据:

currentPage.asObservable()
    // 过滤掉正在加载的情况,避免重复请求
    .filter { [weak self] _ in
        return !self?.isLoading.value ?? false
    }
    // 用flatMapLatest确保只保留最新的请求
    .flatMapLatest { [weak self] page -> Observable<[YourDataModel]> in
        guard let self = self else { return .empty() }
        // 标记为正在加载
        self.isLoading.accept(true)
        // 替换成你的实际API请求方法
        return self.fetchDataFromAPI(page: page, pageSize: self.pageSize)
            // 处理请求错误
            .catch { error in
                print("加载失败:\(error.localizedDescription)")
                // 这里可以添加UI提示,比如Toast
                return .empty()
            }
            // 请求结束后,标记为非加载状态
            .finally {
                self.isLoading.accept(false)
            }
    }
    // 合并新旧数据
    .withLatestFrom(dataSource) { newData, oldData in
        return oldData + newData
    }
    // 更新数据源
    .bind(to: dataSource)
    .disposed(by: disposeBag)

// 模拟API请求方法,替换成你真实的Web API调用
private func fetchDataFromAPI(page: Int, pageSize: Int) -> Observable<[YourDataModel]> {
    return Observable.create { observer in
        // 这里写实际的网络请求逻辑,比如用URLSession或者Alamofire
        // 假设请求成功后返回数据数组
        let mockData = (0..<pageSize).map { YourDataModel(id: $0 + (page-1)*pageSize) }
        observer.onNext(mockData)
        observer.onCompleted()
        return Disposables.create()
    }
}

4. 绑定数据源到UITableView

把数据源绑定到表格上,这里提供两种方式:直接用rx.items或者更强大的RxDataSources:

方式一:直接用rx.items

dataSource.asObservable()
    .bind(to: tableView.rx.items(cellIdentifier: "YourCellID", cellType: YourTableViewCell.self)) { index, item, cell in
        // 配置cell内容
        cell.titleLabel.text = "Item \(item.id)"
    }
    .disposed(by: disposeBag)

方式二:用RxDataSources(适合复杂分区场景)

先导入RxDataSources,然后定义分区模型:

import RxDataSources

// 定义分区模型
struct SectionModel: SectionModelType {
    typealias Item = YourDataModel
    var header: String
    var items: [Item]
    
    init(header: String, items: [Item]) {
        self.header = header
        self.items = items
    }
    
    init(original: SectionModel, items: [Item]) {
        self = original
        self.items = items
    }
}

// 配置数据源
let rxDataSource = RxTableViewSectionedReloadDataSource<SectionModel>(
    configureCell: { dataSource, tableView, indexPath, item in
        let cell = tableView.dequeueReusableCell(withIdentifier: "YourCellID", for: indexPath) as! YourTableViewCell
        cell.titleLabel.text = "Item \(item.id)"
        return cell
    },
    titleForHeaderInSection: { dataSource, section in
        return dataSource[section].header
    }
)

// 绑定数据源
dataSource.asObservable()
    .map { [SectionModel(header: "数据列表", items: $0)] }
    .bind(to: tableView.rx.items(dataSource: rxDataSource))
    .disposed(by: disposeBag)

5. 初始加载第一页数据

在viewDidLoad里触发第一页数据的加载:

override func viewDidLoad() {
    super.viewDidLoad()
    // 注册cell
    tableView.register(YourTableViewCell.self, forCellReuseIdentifier: "YourCellID")
    // 加载第一页数据
    currentPage.accept(1)
}
额外优化点
  • 无更多数据提示:当hasMoreData变为false时,可以在表格底部添加一个“已加载全部”的footer视图。
  • 下拉刷新:可以结合UIRefreshControl的Rx扩展,实现下拉刷新重置页码和数据源。
  • 错误重试:可以给加载失败的情况添加重试按钮,点击后重新触发当前页码的请求。

内容的提问来源于stack exchange,提问作者robot

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 07:26:44