RxDataSources TableView с несколькими разделами из одного источника API

#ios #swift #rx-swift #rxdatasources

#iOS #swift #rx-swift #rxdatasources

Вопрос:

В настоящее время для наших запросов API мы используем Rx. Примером того, как мы это используем, является:

 let orderRxService = OrderRxService.listAsShop(shopId, status: .active)
    .repeatRequest(delay: 4)
    .observeOn(MainScheduler.instance)
    .subscribe( onNext: { [weak self] orders in
        self?.orders = orders
        self?.tableView.reloadData()
    })
    .disposed(by: disposeBag)
  

При этом будут получены все заказы на given shopId со статусом .active . При каждом обновлении локальный orders объект заменяется, а TableView перезагружается.

Это перезагрузит весь TableView, чего мы хотим избежать. Сейчас я изучаю RxDataSources, но не могу понять, как заставить это работать.

У Order объекта есть другое свойство currentStatus , которое может иметь 3 разных значения. У нас есть TableView с 3 разными разделами, в каждом разделе отображаются все заказы для currentStatus .

Как это должно быть реализовано в RxDataSources? В идеале было бы привязать его к сервису, который я показывал ранее ( OrderRxService.....subscribe().. ).

Что у меня есть сейчас для настройки типов RxDataSources, так это:

 extension Order: IdentifiableType, Equatable {
    public typealias Identity = String

    public var identity: String {
        return String(id)
    }

    public static func == (lhs: Order, rhs: Order) -> Bool {
        return (lhs.timeCreated ?? 0) > (rhs.timeCreated ?? 0)
    }
}

struct OrdersSection {
    var header: String
    var orders: [Order]
}

extension OrdersSection: AnimatableSectionModelType {
    typealias Item = Order
    typealias Identity = String

    var identity: String {
        return header
    }

    var items: [Item] {
        set {
            orders = items
        }
        get {
            return orders
        }
    }

    init(original: OrdersSection, items: [Order]) {
        self = original
        self.items = items
    }
}
  

Что я пытался заставить это работать, так это:

 // I tried to make our local orders a Variable (I don't like this between-step and would like this to be just [Order]).
var orders: Variable<[Order]> = Variable([])


fun viewDidLoad() {
    super.viewDidLoad()

    // Then I set the local orders-variable's value to the new value coming from our Rx service.
    let orderRxDisposable: Disposable = OrderRxService.listAsShop(shopId, status: .active)
        .repeatRequest(delay: 4)
        .observeOn(MainScheduler.instance)
        .map { $0.items }.subscribe( onNext: { [weak self] orders in
            self?.orders.value = orders
        })

    // Here I setup the dataSource
    let dataSource = RxTableViewSectionedAnimatedDataSource<OrdersSection>(
        configureCell: { ds, tv, ip, item in
            let cell = tv.dequeueReusableCell(withIdentifier: "OrderCell", for: ip) as! OrderCell
            cell.addContent(item, tableView: tv, viewController: self, spotDelegate: self)
            return cell
        },

        titleForHeaderInSection: { ds, ip in
            return ds.sectionModels[ip].header
        }
    )

    // Here I set up the three different sections.
    self.orders.asObservable().observeOn(MainScheduler.instance)
        .map { o in
            o.filter { $0.currentStatus == .status_one }
        }
        .map { [OrdersSection(header: "Status one", orders: $0)] }
        .bind(to: self.tableView.rx.items(dataSource: dataSource))

    self.orders.asObservable().observeOn(MainScheduler.instance)
        .map { o in
            o.filter { $0.currentStatus == .status_two }
        }
        .map { [OrdersSection(header: "Status two", orders: $0)] }
        .bind(to: self.tableView.rx.items(dataSource: dataSource))

    self.orders.asObservable().observeOn(MainScheduler.instance)
        .map { o in
            o.filter { $0.currentStatus == .status_three }
        }
        .map { [OrdersSection(header: "Status three", orders: $0)] }
        .bind(to: self.tableView.rx.items(dataSource: dataSource))

}
  

Вероятно, существуют различные аспекты, которые можно улучшить. Например, Variable<[Order]> я хотел бы быть просто [Order] .
И вместо того, чтобы делать это наблюдаемым, можно ли вообще пропустить это и создать три разных раздела, наблюдая за нашим OrderRxService?

Возможно ли было бы иметь что-то вроде:

 OrderRxService.listAsshop(shopId, status: .active).observeOn(MainScheduler.instance)
    // First section
    .map { o in
        o.filter { $0.status == .status_one }
    }
    .map { [OrdersSection(header: "Status one", orders: $0)] }
    .bind(to: self.tableView.rx.items(dataSource: dataSource))
    // Second section
    .map { o in
        o.filter { $0.status == .status_two }
    }
    .map { [OrdersSection(header: "Status two", orders: $0)] }
    .bind(to: self.tableView.rx.items(dataSource: dataSource))
    // Etc...
  

Спасибо за любую помощь!

Ответ №1:

Вы могли бы создать модель следующим образом:

 enum SectionModel {
  case SectionOne(items: [SectionItem])
  case SectionTwo(items: [SectionItem])
  case SectionThree(items: [SectionItem])
}

enum SectionItem {
  case StatusOne()
  case StatusTwo()
  case StatusThree()
}

extension SectionModel: SectionModelType {
  typealias Item = SectionItem

  var items: [SectionItem] {
      switch self {
      case .SectionOne(items: let items):
          return items.map { $0 }
      case .SectionTwo(items: let items):
          return items.map { $0 }
      case.SectionThree(items: let items):
          return items.map { $0 }
      }
  }

  init(original: SectionModel, items: [Item]) {
      switch  original {
      case .SectionOne(items: _):
          self = .SectionOne(items: items)
      case .SectionTwo(items: _):
          self = .SectionTwo(items: items)
      case .SectionThree(items: _):
          self = .SectionThree(items: items)
      }
  }
}
  

и обрабатывать различные элементы в вашем источнике данных

 dataSource = RxCollectionViewSectionedReloadDataSource<SectionModel>(configureCell: { (datasource, collectionView, indexPath, _) in
        switch datasource[indexPath] {
        case .StatusOne:
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: R.reuseIdentifier.statusCellOne, for: indexPath)!
            // do stuff
            return cell
        case .StatusTwo:
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: R.reuseIdentifier.statusCellTwo, for: indexPath)!
            // do stuff
            return cell
        case .StatusThree:
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: R.reuseIdentifier.statusCellThree, for: indexPath)!
            // do stuff
            return cell
        }
    })
  

а затем сопоставьте ваши oders с SectionItem для SectionModel и привяжите его к источнику данных

Комментарии:

1. Спасибо за ваш расширенный ответ! Я чувствую, что это шаг в правильном направлении. Всего несколько вопросов, пока я не получу концепцию (кстати, у меня есть tableView , но я думаю, что это не сильно изменится). Что в этом я вижу Order ? Я не понимаю, где [Order] фильтруется в 3 разных. Я вижу, что есть 3 статуса, но я не могу связать это с Order.status . Похоже, что SectionItem следует изменить на Order , чтобы решить мою проблему? Или это неправильно?

2. И я полагаю, RxCollectionViewSectionedReloadDataSource<DocumentSectionModel> может быть RxTableViewSectionedReloadDataSource<SectionModel> ? 🙂

Ответ №2:

Вам не нужно сопоставлять-> привязывать-> map-> bind… Вы можете просто обработать все это на одной «карте» вот так:

 OrderRxService.listAsshop(shopId, status: .active).observeOn(MainScheduler.instance)
    .map { orders in
        let sections: [OrdersSection] = []

        sections.append(OrdersSection(header: "Status one", orders: orders.filter { $0.status == .status_one })
        
        sections.append(OrdersSection(header: "Status two", orders: orders.filter { $0.status == .status_two })
 
        sections.append(OrdersSection(header: "Status three", orders: orders.filter { $0.status == .status_three })
        
        return sections
    }
    .bind(to: self.tableView.rx.items(dataSource: dataSource))
    .disposed(by: disposeBag)
  

Кроме того, если вы часто используете RxSwift / RxDataSources / etc и нуждаетесь в руководстве, подключение к каналу RxSwift Slack — отличный ресурс:
Приглашение в RxSwift Slack