简体   繁体   中英

RxSwift observing changes in array

Let say we have array of InvoiceDataModel

private let invoices Variable<[InvoiceDataModel]> = Variable([])

class InvoiceDataModel { 
    let id: Variable<Int>
    var entity: Variable<InvoiceDto>
    var isSelected: Variable<Bool> 
}

On tap on checkbox I am changing value of isSelected. What I want to achieve is to react on isSelect change to:

  • calculate total amount of selected items (each entity has var amount: Double )
  • detect if all items in collection are selected

Is it possible to observe whole array and react on single property from element change? Not sure how am I supposed to achieve this.

Probably my approach to this case is totally wrong. However I am not sure how am I supposed to operate here in a different way.

The entity and isSelected variables need to be lets and not vars.

Here's the solution I came up with:

let selectedsAndAmounts = invoices
    .asObservable()
    .flatMapLatest {
        Observable.combineLatest($0.map {
            Observable.combineLatest($0.isSelected.asObservable(), Observable.just($0.amount)) { (isSelected: $0, amount: $1) }
        })
    }

let allSelected = selectedsAndAmounts
    .map { $0.map { $0.isSelected } }
    .map { $0.contains(false) == false }

let amountOfSelected = selectedsAndAmounts
    .map { $0.map { $0.isSelected ? $0.amount : 0 } }
    .map { $0.reduce(0, +) }

Most of the complexity in this solution (the selectedsAndAmounts observable) comes from having to unwrap the observables inside observables. It would be better if you could break this up a bit, or remove the Variables from inside the InvoiceDataModel.

First off, note that Variable is deprecated in RxCocoa (use BehaviorRelay instead).

A brief solution would to combine the isSelected observables into a single, amount emitting observable. I just threw this snippet together, but it should point you in the right direction.

invoices
    // whenever the list changes, subscribe to the combined observable (and dispose of any previous subscriptions)
    .flatMapLatest { list in
        // merge all isSelected observables together
        return Observable.merge(list.map { $0.isSelected })
    }
    // when an element is emitted, that means some `isSelected` observable has changed its value
    // get the latest invoices array 
    .withLatestFrom(invoices)
    // convert it from a InvoiceDataModel array to a summed amount of all elements
    .map { $0.reduce(0) { $0 + $1.amount } }
    // log all events passing through
    .debug()
    .subscribe()
    .disposed(by: yourDisposeBag)

You can use a similar approach to subscribe to an Observable that emits if all items are selected. (Don't forget about .distinctUntilChanged() , otherwise you're going to see a lot of emitted elements.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM