Я нахожусь в процессе миграции существующего приложения с использованием MVC, который интенсивно использует шаблон делегирования для MVVM, используя RxSwift и RxCocoa для привязки данных.
В целом каждый контроллер представления владеет экземпляромвыделенный объект View Model. Давайте назовем View Model MainViewModel
для обсуждения. Когда мне нужна модель представления, которая управляет UITableView, я обычно создаю CellViewModel
как struct
, а затем создаю наблюдаемую последовательность, которая преобразуется в драйвер, который я могу использовать для управления табличным представлением.
Теперь предположим, что UITableViewCell содержит кнопку, которую я хотел бы привязать к MainViewModel
, чтобы я мог затем вызвать что-то на моем уровне интерактора (например, вызвать сетевой запрос). Я не уверен, какой шаблон лучше всего использовать в этой ситуации.
Вот упрощенный пример того, с чего я начал (см. 2 конкретных вопроса ниже, пример кода):
Модель основного вида:
class MainViewModel {
private let buttonClickSubject = PublishSubject<String>() //Used to detect when a cell button was clicked.
var buttonClicked: AnyObserver<String> {
return buttonClickSubject.asObserver()
}
let dataDriver: Driver<[CellViewModel]>
let disposeBag = DisposeBag()
init(interactor: Interactor) {
//Prepare the data that will drive the table view:
dataDriver = interactor.data
.map { data in
return data.map { MyCellViewModel(model: $0, parent: self) }
}
.asDriver(onErrorJustReturn: [])
//Forward button clicks to the interactor:
buttonClickSubject
.bind(to: interactor.doSomethingForId)
.disposed(by: disposeBag)
}
}
Модель вида ячейки:
struct CellViewModel {
let id: String
// Various fields to populate cell
weak var parent: MainViewModel?
init(model: Model, parent: MainViewModel) {
self.id = model.id
//map the model object to CellViewModel
self.parent = parent
}
}
Контроллер вида:
class MyViewController: UIViewController {
let viewModel: MainViewModel
//Many things omitted for brevity
func bindViewModel() {
viewModel.dataDriver.drive(tableView.rx.items) { tableView, index, element in
let cell = tableView.dequeueReusableCell(...) as! TableViewCell
cell.bindViewModel(viewModel: element)
return cell
}
.disposed(by: disposeBag)
}
}
Ячейка:
class TableViewCell: UITableViewCell {
func bindViewModel(viewModel: MyCellViewModel) {
button.rx.tap
.map { viewModel.id } //emit the cell's viewModel id when the button is clicked for identification purposes.
.bind(to: viewModel.parent?.buttonClicked) //problem binding because of optional.
.disposed(by: cellDisposeBag)
}
}
Вопросы:
- Есть ли лучший способ сделать то, что яхотите добиться использования этих технологий?
- Я объявил ссылку на родительский элемент в
CellViewModel
как слабую, чтобы избежать цикла сохранения между Cell VM и Main VM. Однако это вызывает проблему при настройке привязки из-за необязательного значения (см. Строку .bind(to: viewModel.parent?.buttonClicked)
в реализации TableViewCell выше.