在昨天的代码中,咱们经过RxSwift对积分排行榜的第一页进行网络请求和数据返回,而后使用数据去驱动页面的加载。编程
固然仅仅1页的加载,对于一个有分页功能的页面是根本是没有意义,作到作好下拉与上拉功能很是重要。数组
这里咱们须要分析一下几点:markdown
集成什么控件去作下拉刷新与上拉加载?网络
下拉刷新逻辑:ide
下拉刷新是要将page的页数重置为第1页,重置footer的状态。布局
对第1页的数据进行网络请求,将获取的数据赋值给数据源dataSource,让其驱动页面。post
网络请求完成,注意无论是成功仍是失败都应该结束下拉刷新的状态。spa
请求完第一页须要判断是否有下页,保持foot的显示与状态:代理
上拉加载是要将page的页数加1。code
对第page + 1页的数据进行网络请求,将获取的数据与以前的dataSourc进行合并,注意是合并,而不是直接赋值,让其驱动页面。
网络请求完成,注意无论是成功仍是失败都应该结束上拉加载更多的状态。
请求完第page + 1页须要判断是否有下页,保持foot的显示与状态:
好了上面的分析作完了,那么就按照这个思路修改代码了,请注意看代码注释喔:
import UIKit
import RxSwift
import RxCocoa
import NSObject_Rx
import Moya
import MJRefresh
class RxSwiftCoinRankListController: BaseViewController {
/// 懒加载tableView
private lazy var tableView = UITableView(frame: .zero, style: .plain)
/// 初始化page为1
private var page: Int = 1
/// 既是可监听序列也是观察者的数据源
private var dataSource: BehaviorRelay<[CoinRank]> = BehaviorRelay(value: [])
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
}
private func setupTableView() {
/// 设置tableFooterView
tableView.tableFooterView = UIView()
/// 设置代理
tableView.rx.setDelegate(self).disposed(by: rx.disposeBag)
/// 设置头部刷新控件
tableView.mj_header = MJRefreshNormalHeader()
tableView.mj_header?.beginRefreshing { [weak self] in
self?.refreshAction()
}
/// 设置尾部刷新控件
tableView.mj_footer = MJRefreshBackNormalFooter()
tableView.mj_footer?.beginRefreshing { [weak self] in
self?.loadMoreAction()
}
/// 简单布局
view.addSubview(tableView)
tableView.snp.makeConstraints { make in
make.edges.equalTo(view)
}
/// 数据源驱动
dataSource
.asDriver(onErrorJustReturn: [])
.drive(tableView.rx.items) { (tableView, row, coinRank) in
if let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") {
cell.textLabel?.text = coinRank.username
cell.detailTextLabel?.text = coinRank.coinCount?.toString
return cell
}else {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "Cell")
cell.textLabel?.text = coinRank.username
cell.detailTextLabel?.text = coinRank.coinCount?.toString
return cell
}
}
.disposed(by: rx.disposeBag)
}
}
extension RxSwiftCoinRankListController {
/// 下拉刷新行为
private func refreshAction() {
resetCurrentPageAndMjFooter()
getCoinRank(page: page)
}
/// 上拉加载更多行为
private func loadMoreAction() {
page = page + 1
getCoinRank(page: page)
}
/// 下拉的参数与状态重置行为
private func resetCurrentPageAndMjFooter() {
page = 1
self.tableView.mj_footer?.isHidden = false
self.tableView.mj_footer?.resetNoMoreData()
}
/// 网络请求
private func getCoinRank(page: Int) {
myProvider.rx.request(MyService.coinRank(page))
/// 转Model
.map(BaseModel<Page<CoinRank>>.self)
/// 因为须要使用Page,因此return到$0.data这一层,而不是$0.data.datas
.map{ $0.data }
/// 解包
.compactMap { $0 }
/// 转换操做
.asObservable()
.asSingle()
/// 订阅
.subscribe { event in
/// 订阅事件
/// 经过page的值判断是下拉仍是上拉(能够用枚举),无论成功仍是失败都结束刷新状态
page == 1 ? self.tableView.mj_header?.endRefreshing() : self.tableView.mj_footer?.endRefreshing()
switch event {
case .success(let pageModel):
/// 解包数据
if let datas = pageModel.datas {
/// 经过page的值判断是下拉仍是上拉,作数据处理,这里为了方便写注释,没有使用三目运算符
if page == 1 {
/// 下拉作赋值运算
self.dataSource.accept(datas)
}else {
/// 上拉作合并运算
self.dataSource.accept(self.dataSource.value + datas)
}
}
/// 解包curPage与pageCount
if let curPage = pageModel.curPage, let pageCount = pageModel.pageCount {
/// 若是发现它们相等,说明是最后一个,改变foot而状态
if curPage == pageCount {
self.tableView.mj_footer?.endRefreshingWithNoMoreData()
}
}
case .error(_):
/// error占时不作处理
break
}
}.disposed(by: rx.disposeBag)
}
}
extension RxSwiftCoinRankListController: UITableViewDelegate {}
复制代码
以上代码已经写完了完善的注释,这里单独说一下:
private var dataSource: BehaviorRelay<[CoinRank]> = BehaviorRelay(value: [])
中的dataSource
。
不一样于通常的dataSource是一个数组,这里咱们使用了RxSwift中的BehaviorRelay,它既是一个序列也能够是一个观察者,而且能够对数据进行赋值运算。序列能够转为特化序列Driver,并驱动tableView,能够作赋值运算,因而能够将网络请求的数据进行赋值和合并操做,在我上面的代码中很是关键。
其实上面的代码运行起来没有什么问题,只是并不RxSwifty,没有那种rx.xxxx回调的感受。
我我的的理解是,有的是时候不能光顾着面子上的事,先保证功能没有问题了,再来考虑拓展与深度。掌握好基础的知识与技能是基石。
同时,在写上面的代码的时候,我也在考虑如何用一个值去绑定tableView,经过状态来改变header与footer的UI状态。
这个其实和声明式UI编写的原则一致了,UI = f(state)
。
我继续围绕着MJRefresh与下拉刷新和上拉加载,考虑使用RxSwift对其进行一层,来进行更好Rx编程。
为啥我会抓着一个简单的列表不放:玩安卓App不少页面都是列表,写好一个,其余的均可以按照这个思路编写与复用。
你们加油。