Search code examples
iosswiftuitableviewrx-swift

Making table cell click events align more with "MVVM"


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.


Solution

  • 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.