在昨天的代碼中,咱們經過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不少頁面都是列表,寫好一個,其餘的均可以按照這個思路編寫與複用。
你們加油。