【问题标题】:How do you communicate between a UIViewController and its child UIView using MVVM and RxSwift events?你如何使用 MVVM 和 RxSwift 事件在 UIViewController 和它的子 UIView 之间进行通信?
【发布时间】:2019-06-19 01:55:58
【问题描述】:

我在我的项目中使用 MVVM、Clean Architecture 和 RxSwift。有一个视图控制器,它有一个子 UIView,它是从一个单独的 .xib 文件动态创建的(因为它在多个场景中使用)。因此有两个视图模型,UIViewController 的视图模型和 UIView 的。现在,子视图模型中有一个 Rx 事件,父视图模型应该观察到它,然后它将调用它的一些及其视图模型的函数。代码是这样的:

MyPlayerViewModel:

class MyPlayerViewModel {
    var eventShowUp: PublishSubject<Void> = PublishSubject<Void>()
    var rxEventShowUp: Observable<Void> {
        return eventShowUp
    }
}

我的播放器视图:

class MyPlayerView: UIView {
    var viewModel: MyPlayerViewModel?
    
    setup(viewModel: MyPlayerViewModel) {
        self.viewModel = viewModel
    }
}

MyPlayerSceneViewController:

class MyPlayerSceneViewController: UIViewController {
    @IBOutlet weak var myPlayerView: MyPlayerView!
    @IBOutlet weak var otherView: UIView! 

    var viewModel: MyPlayerSceneViewModel
    fileprivate var disposeBag : DisposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.myPlayerView.viewModel.rxEventShowUp.subscribe(onNext: { [weak self] in
            self?.viewModel.doOnShowUp()
            self?.otherView.isHidden = true
        })
    }
}

如您所见,目前,我将 myPlayerView 的 viewModel 公开给公众,以便父级可以观察其上的事件。这是正确的方法吗?如果没有,还有其他关于更好方法的建议吗?谢谢。

【问题讨论】:

  • 同时使用 MVVM 和 Clean Architecture 是什么意思?
  • 基本上我使用的是带有 Clean Architecture 的 MVVM,使用 RxSwift 作为从一层到上一层的数据绑定(例如,从模型到视图模型,从视图模型到视图),如下所示:github.com/sergdort/CleanArchitectureRxSwift

标签: ios swift mvvm


【解决方案1】:

一般来说,将视图的内容暴露给它的视图控制器并没有什么不好,但是你真的需要两个单独的视图模型吗?你不混合 viewModel 和模型职责吗?

一些想法:

  • 模型不应继承 UIView
  • 您应该避免在视图模型中创建自己的主题。它本身不创建事件,它只处理输入并公开结果。
  • 建议您熟悉BinderDriver

下面是代码示例:

struct PlayerModel {

    let id: Int
    let name: String
}

class MyPlayerSceneViewModel {

    struct Input {
        let eventShowUpTrigger: Observable<Void>
    }

    struct Output {
        let someUIAction: Driver<PlayerModel>
    }

    func transform(input: Input) -> Output {
        let someUIAction = input.eventShowUpTrigger
            .flatMapLatest(fetchPlayerDetails) // Transform input
            .asDriver(onErrorJustReturn: PlayerModel(id: -1, name: "unknown"))

        return Output(someUIAction: someUIAction)
    }

    private func fetchPlayerDetails() -> Observable<PlayerModel> {
        return Observable.just(PlayerModel(id: 1, name: "John"))
    }
}

class MyPlayerView: UIView {

    var eventShowUp: Observable<Void> {
        return Observable.just(()) // Expose some UI trigger
    }

    var playerBinding: Binder<PlayerModel> {
        return Binder(self) { target, player in
            target.playerNameLabel.text = player.name
        }
    }

    let playerNameLabel = UILabel()
}

class MyPlayerSceneViewController: UIViewController {

    @IBOutlet weak var myPlayerView: MyPlayerView!

    private var viewModel: MyPlayerSceneViewModel!
    private var disposeBag: DisposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
        setupBindings()
    }

    private func setupBindings() {
        let input = MyPlayerSceneViewModel.Input(eventShowUpTrigger: myPlayerView.eventShowUp)
        let output = viewModel.transform(input: input)

        // Drive manually
        output
            .someUIAction
            .map { $0.name }
            .drive(myPlayerView.playerNameLabel.rx.text)
            .disposed(by: disposeBag)

        // or to exposed binder
        output
            .someUIAction
            .drive(myPlayerView.playerBinding)
            .disposed(by: disposeBag)
    }
}

【讨论】:

  • 我的错。 viewmodel 实际上并不是从 UIView 派生的。我只是根据我的记忆输入它,因为我手头没有代码。不过现在不能编辑。无论如何,MyPlayerView 类实际上是一个单独的 xib 中的 AV 播放器视图,可以(并且正在)在多个视图控制器中重用。所以我必须在父视图控制器和视图之间分离视图模型。这就是让这有点复杂的原因。而且该项目之前是DriverBinder,所以我们手动创建了PublishSubject。我会尝试了解它们并重构代码。
  • 我在这里发现了不同的场景:1)通过网关进行通信(例如播放器视图模型触发新的字幕下载,父视图模型观察来自网关的用例的字幕更新并独立于播放器视图模型) 2)视图模型之间直接通信:那么父视图模型应该负责播放器视图模型绑定,而不是VC
猜你喜欢
  • 2018-07-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多