如何在RxTableView中清空数据?基于RxCocoa的MVVM无限滚动场景
解决RxCocoa+MVVM下TableView数据清空与无限滚动的问题
核心思路是给scan操作符添加重置触发逻辑,让它能在需要时清空累加的数据源,再重新开始累加新数据。以下是两种可行的实现方案:
方案一:用枚举区分数据更新类型(推荐)
通过在ViewModel中定义事件枚举,明确区分「重置数据」和「追加数据」两种操作,让scan能清晰处理不同场景:
ViewModel 代码调整
import RxSwift import RxCocoa // 定义列表更新事件类型 enum HotDealUpdateEvent { case reset([HotDeal]) // 清空并替换为新数据 case append([HotDeal]) // 在现有数据后追加 } class HotDealViewModel { let disposeBag = DisposeBag() // 把原有的hotDealList改成事件类型的BehaviorSubject let hotDealUpdate = BehaviorSubject<HotDealUpdateEvent>(value: .reset([])) func requestData(isReset: Bool) { // 替换成你的实际API调用逻辑 APIService.requestHotDeals() .subscribe(onNext: { [weak self] fetchedDeals in let event = isReset ? HotDealUpdateEvent.reset(fetchedDeals) : HotDealUpdateEvent.append(fetchedDeals) self?.hotDealUpdate.onNext(event) }) .disposed(by: disposeBag) } }
ViewController 绑定逻辑
import RxSwift import RxCocoa class HotDealViewController: UIViewController { let disposeBag = DisposeBag() var viewModel = HotDealViewModel() @IBOutlet weak var tableView: UITableView! let refreshControl = UIRefreshControl() override func viewDidLoad() { super.viewDidLoad() setupTableView() bindData() // 首次加载:重置数据 viewModel.requestData(isReset: true) } private func setupTableView() { tableView.register(UINib(nibName: "HotDealCell", bundle: nil), forCellReuseIdentifier: "HotDealCell") tableView.addSubview(refreshControl) } private func bindData() { // 用scan处理事件,实现清空/累加逻辑 viewModel.hotDealUpdate .scan([]) { currentData, event in switch event { case .reset(let newData): return newData // 清空现有数据,替换为新数据 case .append(let moreData): return currentData + moreData // 追加新数据 } } .bind(to: tableView.rx.items(cellIdentifier: "HotDealCell", cellType: HotDealCell.self)) { index, deal, cell in cell.configure(with: deal) } .disposed(by: disposeBag) // 下拉刷新:触发重置 refreshControl.rx.controlEvent(.valueChanged) .subscribe(onNext: { [weak self] in self?.viewModel.requestData(isReset: true) self?.refreshControl.endRefreshing() }) .disposed(by: disposeBag) // 无限滚动(触底加载更多):触发追加 tableView.rx.reachedBottom // 假设你已实现触底检测的扩展方法 .subscribe(onNext: { [weak self] in self?.viewModel.requestData(isReset: false) }) .disposed(by: disposeBag) } }
方案二:合并重置信号与数据信号
如果不想修改ViewModel的输出类型,可以在ViewController中单独定义重置信号,与原有的hotDealList合并后再处理:
ViewController 代码调整
private func bindData() { // 定义重置信号,用于触发清空操作 let resetTrigger = PublishSubject<Void>() // 合并重置信号和数据信号:重置时发送nil,正常加载时发送数据数组 Observable.merge( resetTrigger.map { _ in nil as [HotDeal]? }, viewModel.hotDealList.map { $0 as [HotDeal]? } ) .scan([]) { currentData, newData in guard let newData = newData else { return [] // 收到重置信号,清空数据 } return currentData + newData // 收到数据,累加 } .bind(to: tableView.rx.items(cellIdentifier: "HotDealCell", cellType: HotDealCell.self)) { index, deal, cell in cell.configure(with: deal) } .disposed(by: disposeBag) // 首次加载:先发送重置信号 resetTrigger.onNext(()) viewModel.requestData() // 下拉刷新:触发重置 refreshControl.rx.controlEvent(.valueChanged) .subscribe(onNext: { [weak self] in resetTrigger.onNext(()) self?.viewModel.requestData() self?.refreshControl.endRefreshing() }) .disposed(by: disposeBag) // 触底加载更多 tableView.rx.reachedBottom .subscribe(onNext: { [weak self] in self?.viewModel.requestData() }) .disposed(by: disposeBag) }
关键说明
- 原问题的核心是
scan操作符一直在累加数据,没有重置的触发条件,所以必须引入外部信号或明确的事件类型来通知scan清空累加器。 - 方案一更符合MVVM的职责分离原则,把数据更新的语义封装在ViewModel中,ViewController只负责UI触发和绑定。
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

