RxDataSources tableView with multiple sections fro

2020-08-01 07:22发布

问题:

Currently for our API requests we use Rx. An example of how we use it is:

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)

This gets all orders for given shopId with the status .active. On every update the local orders object is replaced and the tableView is reloaded.

This reload the whole tableView, which we wanna avoid. I'm now looking into RxDataSources but can't really figure out what is the way to get this working.

An Order object has another property currentStatus, which can be 3 different values. What we have is a tableView with 3 different sections, each section displaying all orders for a currentStatus.

How should this be implemented in RxDataSources? Ideally would be to bind it to the service I showed earlier (OrderRxService.....subscribe()..).

What I have now to setup the RxDataSources-types is:

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
    }
}

What I tried to make it work is:

// 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))

}

There are probably different aspects that can be improved. For example the Variable<[Order]> I would like to be just [Order]. And instead of making this observable, could that be skipped altogether and create the three different sections by observing our OrderRxService?

Would it be possible to have it something like:

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...

Thanks for any help!

回答1:

You could create a model like so:

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)
      }
  }
}

and handle the different items in your datasource

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
        }
    })

and then map your oders to the SectionItem for the SectionModel and bind it to the dataSource