I have a table that is being populated from a shiftCells
array in my view model. There are ~8 different types of cells that are rendered, and I need to perform a different action for each of them depending on which cell is clicked. I'm trying to figure out the best way to handle this in the view model, so that the view controller only cares about the end result (so it can navigate to a new view controller, etc...), but I'm struggling to figure out a way to organize it properly. Here's what my current tableview click binding looks like in the VC:
shiftsTableViewController
.cellSelection
.subscribe(onNext: { [weak self] (cellType: ShiftSelectionsTableViewCellType) in
guard let sSelf = self else { return }
switch cellType {
case .cellTypeA(let viewModel):
self.performSomeAction(viewModel: viewModel)
...etc...
}
}).disposed(by: disposeBag)
Based on the type of cell, I perform a custom action (it might be a network request, to which the result would go into a new VC that is navigated to, or it may be something else). Here's what performSomeAction
could look like:
func performSomeAction(viewModel: ShiftTypeCellViewModel) {
self.networkService
.perform(
shiftId: viewModel.shiftId, // notice i need data from cell vm
pin: self.viewModel.pin, // i also need data from root vm
photoURL: self.viewModel.imageURL)
.subscribe(onNext: { result in
self.navigateToPage(result: result)
}, onError: { error in
// show error banner
}).disposed(by: disposeBag)
}
So with ~8 different types of cells, I have 8 methods that all do something a little bit differently. Should I move these methods into the VM? Should I move this methods into the cells VM and handle the click event directly in the cell view? Advice / an example would be appreciated.
Based on what you provided in your question, here's what I think I would end up with:
In viewDidLoad of VC:
let result = viewModel.generateRequest(forCell: tableView.rx.modelSelected(ShiftSelectionsTableViewCellType.self).asObservable())
.flatMapLatest { [networkService] arg in
(networkService?.perform(shitId: arg.shiftId, pin: arg.pin, photoURL: arg.photoURL) ?? .empty())
.materialize()
}
.share(replay: 1)
result.compactMap { $0.element }
.bind(onNext: { [weak self] result in
self?.navigateToPage(result: result)
})
.disposed(by: disposeBag)
result.compactMap { $0.error }
.bind(onNext: { error in
// show error banner
})
.disposed(by: disposeBag)
In ViewModel:
func generateRequest(forCell cell: Observable<ShiftSelectionsTableViewCellType>) -> Observable<(shiftId: String, pin: String, photoURL: URL)> {
return cell.map { [pin, imageURL] cellType in
switch cellType {
case .cellTypeA(let viewModel):
return (shiftId: viewModel.shiftId, pin: pin, photoURL: imageURL)
}
// etc...
}
}
This way, all the side effects are in the VC and all the logic is in the VM. The above puts the networkService
in the VC because it is a side effecting object.