使用 RxSwift 刷新下一页

RefreshNextPage with RxSwift

我正在使用 RxSwift 拉动刷新和刷新下一页。

目前,这是我目前使用的 viewModel:

public final class MomentViewModel {
// Property
let refreshTrigger = PublishSubject<Void>()
let loadNextPageTrigger = PublishSubject<Void>()
let loading = Variable<Bool>(false)
let posts = Variable<[Post]>([])
var pageIndex: Int = 0
let error = PublishSubject<Swift.Error>()
private let disposeBag = DisposeBag()

public init() {

    let refreshRequest = loading.asObservable()
        .sample(refreshTrigger)
        .flatMap { loading -> Observable<Int> in
            if loading {
                return Observable.empty()
            } else {
                return Observable<Int>.create { observer in
                    self.pageIndex = 0
                    print("reset page index to 0")
                    observer.onNext(0)
                    observer.onCompleted()
                    return Disposables.create()
                }
            }
    }
    .debug("refreshRequest", trimOutput: false)

    let nextPageRequest = loading.asObservable()
        .sample(loadNextPageTrigger)
        .flatMap { loading -> Observable<Int> in
            if loading {
                return Observable.empty()
            } else {
                return Observable<Int>.create { [unowned self] observer in
                    self.pageIndex += 1
                    print(self.pageIndex)
                    observer.onNext(self.pageIndex)
                    observer.onCompleted()
                    return Disposables.create()
                }
            }
    }
    .debug("nextPageRequest", trimOutput: false)

    let request = Observable.merge(refreshRequest, nextPageRequest)
        .debug("Request", trimOutput: false)

    let response = request.flatMapLatest { page in
            RxAPIProvider.shared.getPostList(page: page).materialize()
        }
        .share(replay: 1)
        .elements()
        .debug("Response", trimOutput: false)

    Observable
        .combineLatest(request, response, posts.asObservable()) { request, response, posts in
            return self.pageIndex == 0 ? response : posts + response
        }
        .sample(response)
        .bind(to: posts)
        .disposed(by: disposeBag)

    Observable
        .merge(request.map{ _ in true },
            response.map { _ in false },
            error.map { _ in false })
        .bind(to: loading)
        .disposed(by: disposeBag)
    }
}

refreshTriggerloadNextPageTrigger绑定到不同的目标喜欢:

self.tableView.rx_reachedBottom
        .map { _ in () }
        .bind(to: self.viewModel.loadNextPageTrigger)
        .disposed(by: disposeBag)

self.refreshControl.rx.controlEvent(.valueChanged)
        .bind(to: self.viewModel.refreshTrigger)
        .disposed(by: disposeBag)


self.rx.sentMessage(#selector(UIViewController.viewWillAppear(_:)))
        .map { _ in () }
        .bind(to: viewModel.refreshTrigger)
        .disposed(by: disposeBag)

问题:

当我将 tableView 滚动到底部并触发 loadNextPageTrigger 时,一切正常。

但是,如果下一个请求没有更多的数据,loadnextPageTrigger将被无限触发。 任何帮助将不胜感激。

您可以在此处下载 Demo

主要思想是检查是否加载了所有元素。我在源代码中发现你加载了 20 个部分,所以这个条件应该可以正常工作 if loadedPosts < 20 then stop loading new part。在这里,我与您分享基本解决方案,您可以根据需要重构(因为我不擅长RxSwift):

MomentViewModel 你应该声明 属性

private var isAllLoaded = false

如果加载所有值,则设置为 true。然后你应该检查每个 [Post] 响应设置正确的 isAllLoaded:

Observable
    .combineLatest(request, response, posts.asObservable()) { request, response, posts in
        self.isAllLoaded = response.count < 20 // here the check
        return self.pageIndex == 0 ? response : posts + response
    }
    .sample(response)
    .bind(to: posts)
    .disposed(by: disposeBag)

然后在 nextPageRequest 中你应该 return .empty() observer 如果所有部分都已加载:

if loading {
    return Observable.empty()
} else {
    guard !self.isAllLoaded else { return Observable.empty() }

    return Observable<Int>.create { [unowned self] observer in
        self.pageIndex += 1
        print(self.pageIndex)
        observer.onNext(self.pageIndex)
        observer.onCompleted()
        return Disposables.create()
    }
}  

P.S。 MomentViewModel.swift 文件到 copy/paste.