gpt4 book ai didi

ios - 使用 RxSwift 将 UITableViewCell 中的控件绑定(bind)到 ViewModel 的最佳实践

转载 作者:行者123 更新时间:2023-12-01 16:01:43 30 4
gpt4 key购买 nike

我正在使用 MVC 迁移现有应用程序,该应用程序大量使用委托(delegate)模式到使用 RxSwift 和 RxCocoa 进行数据绑定(bind)的 MVVM。

一般来说,每个 View Controller 都拥有一个专用 View Model 对象的实例。我们将 View 模型称为 MainViewModel用于讨论目的。当我需要一个驱动 UITableView 的 View Model 时,我通常会创建一个 CellViewModel作为 struct然后创建一个可观察的序列,该序列转换为我可以用来驱动 TableView 的驱动程序。

现在,假设 UITableViewCell 包含一个我想绑定(bind)到 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)
}
}

单元格 View 型号:
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
}
}

查看 Controller :
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)时由于可选值而导致问题(参见上面 TableViewCell 实现中的 .bind(to: viewModel.parent?.buttonClicked) 行。
  • 最佳答案

    这里的解决方案是将 Subject 移出 ViewModel 并移入 ViewController。如果您发现自己在 View 模型中使用了 Subject 或 dispose bag,那么您可能做错了什么。有异常(exception),但它们非常罕见。你当然不应该把它作为一种习惯。

    class MyViewController: UIViewController {
    var tableView: UITableView!
    var viewModel: MainViewModel!
    private let disposeBag = DisposeBag()

    func bindViewModel() {
    let buttonClicked = PublishSubject<String>()
    let input = MainViewModel.Input(buttonClicked: buttonClicked)
    let output = viewModel.connect(input)
    output.dataDriver.drive(tableView.rx.items) { tableView, index, element in
    var cell: TableViewCell! // create and assign
    cell.bindViewModel(viewModel: element, buttonClicked: buttonClicked.asObserver())
    return cell
    }
    .disposed(by: disposeBag)
    }
    }

    class TableViewCell: UITableViewCell {
    var button: UIButton!
    private var disposeBag = DisposeBag()
    override func prepareForReuse() {
    super.prepareForReuse()
    disposeBag = DisposeBag()
    }

    func bindViewModel<O>(viewModel: CellViewModel, buttonClicked: O) where O: ObserverType, O.Element == String {
    button.rx.tap
    .map { viewModel.id } //emit the cell's viewModel id when the button is clicked for identification purposes.
    .bind(to: buttonClicked) //problem binding because of optional.
    .disposed(by: disposeBag)
    }
    }

    class MainViewModel {

    struct Input {
    let buttonClicked: Observable<String>
    }

    struct Output {
    let dataDriver: Driver<[CellViewModel]>
    }

    private let interactor: Interactor

    init(interactor: Interactor) {
    self.interactor = interactor
    }

    func connect(_ input: Input) -> Output {
    //Prepare the data that will drive the table view:
    let dataDriver = interactor.data
    .map { data in
    return data.map { CellViewModel(model: $0) }
    }
    .asDriver(onErrorJustReturn: [])

    //Forward button clicks to the interactor:
    _ = input.buttonClicked
    .bind(to: interactor.doSomethingForId)
    // don't need to put in dispose bag because the button will emit a `completed` event when done.

    return Output(dataDriver: dataDriver)
    }
    }

    struct CellViewModel {
    let id: String
    // Various fields to populate cell

    init(model: Model) {
    self.id = model.id
    }
    }

    关于ios - 使用 RxSwift 将 UITableViewCell 中的控件绑定(bind)到 ViewModel 的最佳实践,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58597531/

    30 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com