StoresViewController:
class StoresViewController: BasicViewController, RouterType {
@IBOutlet weak var tableView: UITableView!
let presentationModel: StoresPresentationModel = StoresPresentationModel()
let storesSectionSubject: BehaviorSubject<[StoreViewModel]> = BehaviorSubject(value: [])
var router: AnyRouter! {
return StoresRouter(viewController: self)
}
var storesViewModels: [StoreViewModel] = []
override func viewDidLoad() {
super.viewDidLoad()
configureHUD()
configureTableView()
configureDataFlow()
}
func configureHUD() {
PKHUD.sharedHUD.contentView = PKHUDSystemActivityIndicatorView()
presentationModel.networkIsActive
.subscribe(onNext: { active in
switch active {
case true:
PKHUD.sharedHUD.show()
case false:
PKHUD.sharedHUD.hide()
}
}).addDisposableTo(disposeBag)
}
//Что отображается в таблице и как
private func configureTableView() {
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 51
let dataSource = RxTableViewSectionedReloadDataSource<SectionModel>()
skin(dataSource)
storesSectionSubject
.do(onNext: { [unowned self] viewModels in self.storesViewModels = viewModels })
.map{ [SectionModel(items: $0)] }
.bindTo(tableView.rx.items(dataSource: dataSource))
.addDisposableTo(disposeBag)
tableView.rx
.modelSelected(StoreViewModel.self)
.subscribe(
onNext: { [unowned self] viewModel in
//Эта логика должна была оказаться в Routerе
self.performSegue(withIdentifier: "StoreDetailed", sender: self.storesViewModels.map{StoreDetailedViewModel(store: $0.store, userLocation: $0.userLocation)})
}
).addDisposableTo(disposeBag)
}
private func configureDataFlow() {
presentationModel
.getStoresSortedByDistance()
.subscribe(
onNext: { [unowned self] viewModels in
self.storesSectionSubject.on(.next(viewModels))
}
).addDisposableTo(disposeBag)
}
private func skin(_ dataSource: RxTableViewSectionedReloadDataSource<SectionModel>) {
dataSource.configureCell = { dataSource, tableView, indexPath, storeViewModel in
let cell = tableView.dequeueReusableCellOfType(StoreCell.self, for: indexPath)
cell.storeNameLabel.text = storeViewModel.title
cell.distanceToStoreLabel.text = storeViewModel.distanceTo
return cell
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? RouterType {
router.pass(sender, to: destination.router)
}
}
}
fileprivate struct SectionModel {
var items: [StoreViewModel]
}
extension SectionModel: SectionModelType {
typealias Item = StoreViewModel
init(original: SectionModel, items: [Item]) {
self = original
self.items = items
}
}
StoresPresentationModel:
class StoresPresentationModel: BasicPresentationModel {
private let service = StoresService()
var targetCity: City! = nil
private func getAllStores() -> Observable<[Store]> {
return service
.getAllStores(.api)
.map { $0.storeList }
}
private func getStoresForChosenCity() -> Observable<[Store]> {
return
getAllStores()
.map { [unowned self] stores in
return stores.filter{ $0.cityID == self.targetCity.id }
}
}
func getStoresSortedByDistance() -> Observable<[StoreViewModel]> {
networkIsActive.on(.next(true))
return
Observable.combineLatest(
getStoresForChosenCity(),
RxSwiftLocation()
.getLocationOnce()
.startWith(nil)
//.timeout?
.catchErrorJustReturn(nil)
) { stores, location in
return (stores, location)
}
.map { (stores, location) in
var stores = stores
if let location = location {
stores.sort(by: { first, second in
return first.coordinate.toCLLocation().distance(from: location) < second.coordinate.toCLLocation().distance(from: location)
})
}
return stores.map{ StoreViewModel(store: $0, userLocation: location) }
}
.doOnDispose { [unowned self] in
self.networkIsActive.on(.next(false))
}
}
}
StoresService:
class StoresService: BasicService<StoresTarget> {
let dao = try! ArrayStoresResponseDAO()
func getAllStores(_ strategy: RequestStrategy) -> Observable<StoresResponse> {
switch strategy {
case .api:
return getAllStoresFromDAO().concat(getAllStoresFromAPI())
case .cached:
return getAllStoresFromDAO()
}
}
private func getAllStoresFromAPI() -> Observable<StoresResponse> {
return
provider
.request(.getAllStores)
.observeOn(utilityScheduler)
.timeout(defaultRequestTimeout, scheduler: utilityScheduler)
.mapResponse(StoresResponse.self)
//Сохранение в json файл
.do(
onNext: { [unowned self] response in
try self.dao.persist(entity: response)
})
.observeOn(MainScheduler.instance)
}
private func getAllStoresFromDAO() -> Observable<StoresResponse> {
return
Observable
.just()
.observeOn(utilityScheduler)
.map { [unowned self] in
guard let response = self.dao.getAll().last else { throw NSError(domain: "DAO is empty?", code: 404, userInfo: nil) }
return response
}
.observeOn(MainScheduler.instance)
}
}
Тот самый mapResponse про который я рассказывал:
extension ObservableType where E: Moya.Response {
func mapResponse<T: Mappable & HeaderType>(_ type: T.Type) -> Observable<T> {
return flatMap { moyaResponse -> Observable<T> in
let wrapper = try moyaResponse.mapObject(MoyaResponseWrapper<T>.self)
switch wrapper {
case let .error(error):
throw error
case var .response(response):
guard
let res = moyaResponse.response as? HTTPURLResponse,
let headers = res.allHeaderFields as? [String : String] else {
throw RxError.unknown
}
response.headers = headers
return Observable.just(response)
}
}
}
}