RxSwift наблюдает за изменениями в массиве

Допустим, у нас есть массив InvoiceDataModel

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

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

При установке флажка я изменяю значение isSelected. Я хочу отреагировать на изменение isSelect на:

  • рассчитать общее количество выбранных предметов (у каждого объекта есть var amount: Double)
  • определить, все ли элементы в коллекции выбраны

Можно ли наблюдать за всем массивом и реагировать на отдельное свойство при изменении элемента? Не знаю, как мне этого добиться.

Наверное, мой подход к этому делу совершенно неверен. Однако я не уверен, как я должен действовать здесь по-другому.


person Piotr Gawłowski    schedule 04.07.2018    source источник


Ответы (2)


Переменные entity и isSelected должны быть let, а не vars.

Вот решение, которое я придумал:

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

Большая часть сложности в этом решении (наблюдаемые selectedsAndAmounts) возникает из-за необходимости развернуть наблюдаемые внутри наблюдаемых. Было бы лучше, если бы вы могли немного разбить это или удалить переменные из InvoiceDataModel.

person Daniel T.    schedule 23.11.2018

Прежде всего, обратите внимание, что Variable устарела в RxCocoa (вместо этого используйте BehaviorRelay).

Краткое решение - объединить isSelected наблюдаемые в единую, излучающую наблюдаемую величину. Я просто собрал этот фрагмент, но он должен указать вам правильное направление.

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)

Вы можете использовать аналогичный подход для подписки на Observable, который излучает, если выбраны все элементы. (Не забывайте о .distinctUntilChanged(), иначе вы увидите много излучаемых элементов.)

person CloakedEddy    schedule 09.08.2018