使表格单元格单击事件与“ MVVM”更一致

时间:2019-07-17 17:36:21

标签: ios swift uitableview rx-swift

我有一个从视图模型中的shiftCells数组填充的表。渲染了约8种不同类型的单元格,我需要根据单击的单元格对每个单元格执行不同的操作。我试图找出在视图模型中处理此问题的最佳方法,以便视图控制器仅关心最终结果(以便它可以导航到新的视图控制器,等等。),但是努力寻找一种适当地组织它的方法。这是我当前的tableview点击绑定在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)

根据单元格的类型,我执行一个自定义操作(它可能是一个网络请求,其结果将进入导航到的新VC,或者可能是其他东西)。 performSomeAction如下所示:

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

因此,在〜8种不同类型的单元格中,我有8种方法,它们的处理方式略有不同。我应该将这些方法移到虚拟机中吗?我是否应该将此方法移到单元VM中并直接在单元视图中处理click事件?忠告/一个例子,将不胜感激。

2 个答案:

答案 0 :(得分:1)

您的viewController应该只处理带有单元格选择的UI,然后您的视图模型应该是数据的辅助者。另外,如果可能的话,我会尽量避免让您的单元具有UI代码以外的内容。

// ViewController
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.deselectRow(at: indexPath, animated: true)
    viewModel.performSomeAction(indexPath: indexPath)
}

func setupObservables() {
    viewModel.outputs.reloadRelay
        .subscribeOn(MainScheduler.instance)
        .observeOn(MainScheduler.instance)
        .subscribe(onNext: { [weak self] _ in
            guard let uwSelf = self else { return }
            uwSelf.tableView.reloadData()
        }).disposed(by: disposeBag)
}


// ViewModel

// This BehaviorRelay is kinda silly but i'm and trying to demonstrate a trigger that your viewcontroller can listen for to update your UI
var reloadRelay: BehaviorRelay<Bool> = BehaviorRelay(value: false)

func performSomeAction(indexPath: IndexPath) {
    switch (indexPath.section, indexPath.row) {
    case (1, _):
    // Make a network call then update your data
    service.randomCall(id: id)
        .subscribeOn(MainScheduler.instance)
        .subscribe(onNext: { result in
            // Update data with result response data
            self.navigateToPage(result: result)
    }, onError: { [weak self] error in
        print(error)
    }).disposed(by: disposeBag)

    case (2, _):
    // Update your data then reload the table
    reloadRelay.accept(true)
    case (3, _):
    // Do something else
    case (4, 0):
    // Do something else
    ...
    default: break
    })
}

答案 1 :(得分:1)

根据您在问题中提供的内容,以下是我想得出的结论:

在VC的viewDidLoad中:

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)

在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...
    }
}

这样,所有副作用都在VC中,所有逻辑都在VM中。上面将networkService放入VC中,因为它是一个副作用对象。