本文主要结合前两篇对多分组UITableView
的使用进行介绍,以下是前两篇内容,可直接点击跳转:
1. RxSwift+MVVM项目实战-多分组UITableView结合RxDataSources的使用
2. RxSwift+MVVM项目实战-多分组UITableView+RxDataSources+MJRefresh的使用
效果图
主要是利用RxAlamofire
请求数据,以及HandyJSON
对返回的json
数据做处理;主要分析MuchGroupViewModel
和MuchGroupViewController
,其他模块已在之前文章分析过;
RxAlamofire:基于Swift版本Alamofire的HTTP请求,利用RxSwift进一步封装而来;具体使用方式可参考 https://github.com/RxSwiftCommunity/RxAlamofire
HandyJson:基于Swift编写的字典转模型框架;具体使用方式可参考 https://github.com/alibaba/handyjson
目录结构
MuchGroupViewModel
Input
refresh
:接收点击刷新按钮的事件;
headerRefresh
:接收下拉刷新事件;
footerRefresh
:上拉加载更多事件;
struct Input { let refresh: Observable<Void> let headerRefresh: Observable<Void> let footerRefresh: Observable<Void> }
Output
items
:tableView
的数据源;
endRefreshing
:通知外界tableView
什么情况下结束刷新,以及结束刷新的状态类型;
struct Output { let items: BehaviorRelay<[MuchGroupSection]> let endRefreshing: PublishRelay<LTRefreshEndState> }
RespEvent
保存网络请求返回的数据,以及结束刷新的状态类型;
private enum RespEvent { case resp(_ data: [MuchGroupSection], _ state: LTRefreshEndState) var data: [MuchGroupSection] { switch self { case .resp(let data, _): return data } } var state: LTRefreshEndState { switch self { case .resp(_, let _state): return _state } } }
request函数
利用RxAlamofire
发起网络请求,并返回网络请求经过处理后的数据,以及结束刷新的状态类型;
private func request(pageNum: Int) -> Observable<RespEvent> { let url = URL(string: "https://")! Logger("\(pageNum == 0 ? "下拉刷新" : "上拉加载")发起请求...") return RxAlamofire.requestJSON(.get, url) .map { _ in MuchGroupModel.json } .map(model: MuchGroupModel.self) .map ({ (model) -> RespEvent in Logger("收到请求结果...") var section = [MuchGroupSection]() ... if pageNum > 1 { return RespEvent.resp(section, .noMoreData) }else { return RespEvent.resp(section, .endRefreshing) } }) }
自定义的map函数
给Observable
做扩展,目的是将传入进来的字典,利用Handyjson
转换为模型信号;
extension Observable where Element == [String : Any] { func map<T: HandyJSON>(model: T.Type) -> Observable<T> { self.map { (element) -> T in guard let ret = T.deserialize(from: element) else { fatalError("数据格式不正确") /** 此处自行处理 */ } return ret } } }
transform
items
:保存数据源,保证上下拉请求时,对数据进行操作;
endRefreshing
:保存刷新状态,用于绑定到外界tableView
上;
header
:将刷新事件以及下拉刷新事件合并,任意一个收到事件后,均发起网络请求,并处理数据,然后绑定到items
上,之后items
抛出给外界,用于提供tableView
的数据源,另外也将状态绑定到endRefreshing
上,用于结束tableView
的刷新;
footer
:收到上拉加载更多的时候,发起请求,并将之前的数据和新的数据进行拼接,然后绑定到items
上,另外也将状态绑定到endRefreshing
上,用于结束tableView
的刷新;
func transform(input: Input) -> Output { let items = BehaviorRelay<[MuchGroupSection]>(value: []) let endRefreshing = PublishRelay<LTRefreshEndState>() //下拉刷新数据,请求第0页的数据并转换为Observable<RespEvent>类型 let header = Observable.of(input.refresh, input.headerRefresh).merge().flatMap { [unowned self] (v1) -> Observable<RespEvent> in self.pageNum = 0 return self.request(pageNum: self.pageNum) }.share(replay: 1, scope: .whileConnected) //抛出给外界,用于绑定到tableView数据源 header.map { $0.data }.bind(to: items).disposed(by: disposeBag) //拿到返回数据通知外界停止刷新 header.map { $0.state }.bind(to: endRefreshing).disposed(by: disposeBag) //上拉加载更多数据,请求第1.2.3...页的数据并转换为Observable<RespEvent>类型 let footer = input.footerRefresh.flatMap {[unowned self] (_) -> Observable<RespEvent> in self.pageNum += 1 return self.request(pageNum: self.pageNum) }.share(replay: 1, scope: .whileConnected) //把当前页数据和前几页数据做拼接,并抛出给外界,用于绑定到tableView数据源 footer.map { (event) -> [MuchGroupSection] in guard case .resp(let data, _) = event else { return items.value } return items.value + data }.bind(to: items).disposed(by: disposeBag) //拿到返回数据通知外界停止刷新 footer.map { $0.state }.bind(to: endRefreshing).disposed(by: disposeBag) return Output(items: items, endRefreshing: endRefreshing) }
ViewController
结合RxDataSources
,为tableView
提供数据源以及配置cell
,然后将Output
的items
绑定到tableView
的数据源上,将endRefreshing
变量绑定到前文所分析的scrollView
的扩展上;
private func bindToViewModel() { let output = viewModel.transform(input: MuchGroupViewModel.Input(refresh: Observable.just(()), headerRefresh: tableView.mj_header!.rx.glt_refreshing .asObservable(), footerRefresh: tableView.mj_footer!.rx.glt_refreshing .asObservable())) let dataSource = RxTableViewSectionedReloadDataSource<MuchGroupSection> { [unowned self] (dataSource, tableView, indexPath, item) -> UITableViewCell in switch item { case .banner(let viewModel): let cell: MuchGroupBannerCell = self.cellWithTableView(tableView) //绑定 cell.bind(to: viewModel) //按钮点击事件 cell.openSubjuect.subscribe {[weak self] (_) in self?.openEvent(viewModel.model, indexPath) }.disposed(by: cell.disposeBag) return cell case .live(let viewModel): let cell: MuchGroupLiveCell = self.cellWithTableView(tableView) //绑定 cell.bind(to: viewModel) //按钮点击事件 cell.openSubjuect.subscribe {[weak self] (_) in self?.openEvent(viewModel.model, indexPath) }.disposed(by: cell.disposeBag) return cell } } output.items.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: disposeBag) output.endRefreshing.bind(to: tableView.rx.endRefreshing).disposed(by: disposeBag) tableView.rx.itemSelected.subscribe {[weak self] (value) in if let indexPath = value.element { self?.tableView.deselectRow(at: indexPath, animated: true) } self?.navigationController?.pushViewController(MuchGroupViewController(), animated: true) }.disposed(by: disposeBag) refreshItem.rx.tap.subscribe {[weak self] (_) in self?.tableView.mj_header?.beginRefreshing() }.disposed(by: disposeBag) tableView.rx.setDelegate(self).disposed(by: disposeBag) }