1

I have the Tableview with UIAlertAction. When I press a cell, a pop-up appears and then I can decide which operation to perform. During the Ok operation (button click) I want to observe the data of the cell and receive it in the ViewModel. But there are problems. When I press for the first time there is no value and if I just click a cell the data is still sending to the ViewModel. If I click more UIAlertAction popu-ups the sending data is increasing. For example: First click - nothing, second click - value 1, third click value 1, 1, 1 and so on. How to start observing cells from the first click of the UIAlertAction pop-up and how to get only on example of the data?

ViewController:

func bindTableView() {
    viewModel.stationItems.bind(to: addTableView.rx.items(cellIdentifier: "addCell", cellType: AddTableViewCell.self)) { (row, item, cell)
        in
        cell.cellAdd = item
    }.disposed(by: disposeBag)
    
    addTableView.rx.modelSelected(StationItem.self)
        .subscribe(onNext: { item in

            let alert = UIAlertController(title: "Add Station", message: "Do you want to add a station to your favorites?", preferredStyle: .alert)
            let ok = UIAlertAction(title: "Ok", style: .default, handler: { action in
                self.addTableView.rx.modelSelected(StationItem.self)
                    .bind(to: self.viewModel.stationItem)
                    .disposed(by: self.disposeBag)
                self.viewModel.addStationItem()
            })
            alert.addAction(ok)
            let cancel = UIAlertAction(title: "Cancel", style: .default, handler: { action in
            })
            alert.addAction(cancel)
            DispatchQueue.main.async(execute: {
                self.present(alert, animated: true)
            })
        }).disposed(by: disposeBag)
    
    viewModel.fetchStations()
}

ViewModel:

 let stationItem = PublishSubject<StationItem>()

 func addStationItem() {
    stationItem.subscribe(onNext: {(data) in
        print("Data: \(data)")
        
        })
        .disposed(by: disposeBag)
    
    print("ADD:")
            
}
beginner992
  • 659
  • 1
  • 9
  • 28

1 Answers1

1

The problem is your retroactive subscription to modelSelected. Keep in mind that every time you call the function, you get a new observable. From the moment of subscription on, it will tell you whenever something is tapped, but it won't tell you about the previous tap, the one that caused the alert to appear in the first place.

The first thing to do here is to make a reusable function that let's you create and present alert views. Whenever you want to ask the user for something use this sort of pattern:

extension UIViewController {
    func presentAlert(title: String?, message: String?) -> Observable<Void> {
        let result = PublishSubject<Void>()
        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
        let ok = UIAlertAction(title: "OK", style: .default, handler: { _ in
            result.onNext(())
            result.onCompleted()
        })
        let cancel = UIAlertAction(title: "Cancel", style: .cancel) { _ in
            result.onCompleted()
        }
        alert.addAction(ok)
        alert.addAction(cancel)
        present(alert, animated: true)
        return result
    }
}

If you are going with a imperative inputs for your view model, go all the way for consistency:

final class ViewModel {
    let stationItems = PublishSubject<[StationItem]>()

    func addStationItem(_ item: StationItem) {
        print("ADD:")
    }

    func fetchStations() { 
        // do your fetching and load `stationItems`.
    }
}

Then in the bind function of your view controller, you can use a flatMap to call the above:

addTableView.rx.modelSelected(StationItem.self)
    .flatMapFirst { [unowned self] item in
        self.presentAlert(title: "Add Station", message: "Do you want to add a station to your favorites?")
            .map { item }
    }
    .bind { [viewModel] in
        viewModel?.addStationItem($0)
    }
    .disposed(by: disposeBag)

What you are doing here looks very much like The Binder Architecture. You might find it instructive to read more about it.

Daniel T.
  • 32,821
  • 6
  • 50
  • 72