Search code examples
swiftrx-swift

How to combineLatest only when a particular Observable has changed


I am very new to RxSwift and trying to do the following:

My application needs to enable selection of "Elements" where the selection mode can be single selection (a new selection replaces an old selection) or multiple, where a selection is added to any old selection.

In single mode, if the new selection is the old selection, then my selection results must become empty (toggles the selection by selecting the same element).

In multiple mode, if the new selection is part of the old selection, the newly selected element is removed from the current selection.

I have three existing subjects: selectionModeSubject is a BehaviorSubject containing the single or multiple enumeration. selectSubject represents the new selection requested by the user, it is a PublishSubject. Finally, currentSelectionSubject, a BehaviorSubject containing the current Set of elements that are selected.

I'm trying to have currentSelectionSubject containing the resulting selection after selectSubject fires.

Here is what I have:

Observable
        .combineLatest(selectionModeSubject, selectSubject, currentSelectionSubject) { (mode, newSelection, currentSelection) -> Set<Element> in
            switch mode {
            case .single:
                if currentSelection.contains(newSelection) {
                    return Set([newSelection])
                } else {
                    return Set<Element>()
                }
            case .multiple:
                if currentSelection.contains(newSelection) {
                    return currentSelection.filter({ (element) -> Bool in
                        return element != newSelection
                    })
                } else {
                    return currentSelection.union(Set([newSelection]))
                }
            }
        }
        .bind(to: currentSelectionSubject)
        .disposed(by: disposeBag)

The problem, amplified by my newbie RxSwift status, is that this observation code potentially fires whenever the selectionModeSubject or currentSelectionSubject fire. I would only want this to fire if selectSubject is changed.

I have tried to insert a .distinctUntilChanged() on the selectSubject but I cannot seem to come to grasp with it.

Any tips would be appreciated.


Solution

  • withLatestFrom is the way to go.

    selectSubject.withLatestFrom(
      Observable
        .combineLatest(selectionModeSubject, currentSelectionSubject)) { newSelection, pair in
      let (mode, currentSelection) = pair
    
      return (mode, newSelection, currentSelection)
    }.map { (mode, newSelection, currentSelection) -> Set<Element> in
      switch mode {
      case .single:
        if currentSelection.contains(newSelection) {
          return Set([newSelection])
        } else {
          return Set<Element>()
        }
        case .multiple:
          if currentSelection.contains(newSelection) {
            return currentSelection.filter({ (element) -> Bool in
              return element != newSelection
            })
          } else {
            return currentSelection.union(Set([newSelection]))
          }
        }
      }
      .bind(to: currentSelectionSubject)
      .disposed(by: disposeBag)