Skip to content

Instantly share code, notes, and snippets.

@abdulowork
Created March 17, 2017 09:31
Show Gist options
  • Save abdulowork/3377aab766540af7a8d81a11415dcff5 to your computer and use it in GitHub Desktop.
Save abdulowork/3377aab766540af7a8d81a11415dcff5 to your computer and use it in GitHub Desktop.

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)
      }
    }
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment