使 table 单元格点击事件更符合 "MVVM"
Making table cell click events align more with "MVVM"
我有一个 table 是从我的视图模型中的 shiftCells
数组填充的。渲染了大约 8 种不同类型的单元格,我需要根据单击的单元格对它们中的每一个执行不同的操作。我试图找出在视图模型中处理此问题的最佳方法,以便视图控制器只关心最终结果(因此它可以导航到新的视图控制器等......),但我努力想出一种正确组织它的方法。这是我当前的 table 查看点击绑定在 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 中吗?我是否应该将此方法移动到单元格 VM 中并直接在单元格视图中处理点击事件?建议/示例将不胜感激。
你的 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
})
}
根据您在问题中提供的内容,我认为我最终会得到以下结果:
在 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 { [=10=].element }
.bind(onNext: { [weak self] result in
self?.navigateToPage(result: result)
})
.disposed(by: disposeBag)
result.compactMap { [=10=].error }
.bind(onNext: { error in
// show error banner
})
.disposed(by: disposeBag)
在视图模型中:
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 中,因为它是一个副作用对象。
我有一个 table 是从我的视图模型中的 shiftCells
数组填充的。渲染了大约 8 种不同类型的单元格,我需要根据单击的单元格对它们中的每一个执行不同的操作。我试图找出在视图模型中处理此问题的最佳方法,以便视图控制器只关心最终结果(因此它可以导航到新的视图控制器等......),但我努力想出一种正确组织它的方法。这是我当前的 table 查看点击绑定在 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 中吗?我是否应该将此方法移动到单元格 VM 中并直接在单元格视图中处理点击事件?建议/示例将不胜感激。
你的 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
})
}
根据您在问题中提供的内容,我认为我最终会得到以下结果:
在 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 { [=10=].element }
.bind(onNext: { [weak self] result in
self?.navigateToPage(result: result)
})
.disposed(by: disposeBag)
result.compactMap { [=10=].error }
.bind(onNext: { error in
// show error banner
})
.disposed(by: disposeBag)
在视图模型中:
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 中,因为它是一个副作用对象。