【问题标题】:How to make ViewController observe any changes to ViewModel variables (Variable<..>) with RxSwift?如何使用 RxSwift 让 ViewController 观察 ViewModel 变量(Variable<..>)的任何变化?
【发布时间】:2019-04-27 23:29:12
【问题描述】:

如何从ViewController 观察ViewModel 类中Variable&lt;...&gt; 值(RxSwiftVariable) 的变化?

因此,如果我在ViewModel 中的任何Variable&lt;..&gt; 的值在ViewModel 中发生的事情中发生变化,那么ViewController 将被注意到“嘿!一个或多个变量 在 ViewModel 中发生了变化!向 ViewModel 询问你需要更新 UI 并更新 UI 的数据!”

然后ViewController 在 ViewController 中调用方法 updateUI() 并在其中向 ViewModel 询问所有信息,例如状态/状态,以更新 UI,如下所示:

func updateUI() {
  progressBar.hide = viewModel.getProgressBarVisibility()
  errorMessageLabel.hide = viewModel.getErrorMessageVisibility()
  errorMessageLabel.text = viewModel.getErrorMessageText()
  .....
  ...
}

【问题讨论】:

  • 您可以使用combineLatest 运算符,以便您的任何可观察对象发生的任何更改都会触发它,您可以将其值分配给viewModel 中的属性并在viewController 中订阅它。还有不推荐使用变量尝试使用subjects
  • 仅供参考。 Observables 是一个“告诉不问”的系统。您的视图控制器不应“向 ViewModel 询问数据”。相反,视图控制器告诉 ViewModel 中的变量他们对更改感兴趣(通过 subscribe 函数),然后变量告诉视图控制器新值是什么因为他们被改变了。当然,Variable 已被弃用。

标签: ios swift observer-pattern rx-swift


【解决方案1】:

我们可以构造一个双向绑定运算符,您只需使用 bindTo 即可。以下是 ControlProperty 变量和变量 变量的实现:

infix operator <->

@discardableResult func <-><T>(property: ControlProperty<T>, variable: BehaviorSubject<T>) -> Disposable {
    let variableToProperty = variable.asObservable()
        .bind(to: property)

    let propertyToVariable = property
        .subscribe(
            onNext: { variable.onNext($0) },
            onCompleted: { variableToProperty.dispose() }
    )

    return Disposables.create(variableToProperty, propertyToVariable)
}

您可以在以下帖子中找到问题的详细答案。 Two way binding in RxSwift

【讨论】:

    【解决方案2】:

    要更新您的 UI,我建议使用 viewState 变量,您可以在需要时在视图模型类中更新该变量,例如:

    /// Making it generic allow you to add your view specific state
    public enum ViewState<T> {
        // add all the case you need
        case loading
        case ready(T)
        case failure(Error)
    }
    

    然后在你的 viewModel 类中:

    let viewState: Variable<ViewState<YourViewControllerState>> = Variable<ViewState<YourViewControllerState>>(.loading)
    

    YourViewControllerState 是您的具体案例的枚举:

    enum YourViewControllerState {
        case progressBarShowed, //...
    }
    

    最后在你的 ViewController 中:

    viewModel.viewState
        .asObservable()
        .observeOn(MainScheduler.instance)
        .subscribe { [weak self] _ in
            self?.updateUI()
        }.disposed(by: disposeBag)
    
    private func updateUI() {
        guard isViewLoaded else {
            return
        }
    
        switch viewModel.viewState.value {
        case .ready(.progressBarShowed):
            progressBar.hide = viewModel.getProgressBarVisibility()
    
        case .failure:
            errorMessageLabel.hide = viewModel.getErrorMessageVisibility()
            errorMessageLabel.text = viewModel.getErrorMessageText()
        }
    }
    

    【讨论】:

    • 我想为我的具体情况寻找不同的解决方案。所以我想检查一种方法,每次在 ViewModel 中定义的所有 Variable<...> 中的值发生变化时,从 ViewModel 触发一种类型的事件。
    【解决方案3】:

    如果单个 ViewModel 的属性值发生变化,为什么要更新整个 UI?

    RxSwift 使您能够独立监听更改,并且您可以相应地做出反应/更改 UI。

    在我看来,您的 ViewModel 和 ViewController 类应该是这样的:

    class ViewModel {
        private var progressBarVisibiity:Variable<Double> = Variable.init(0.0)
        private var errorMessageVisibiity:Variable<Double> = Variable.init(0.0)
        private var errorMessageLabel:Variable<String> = Variable.init("Default text")
    
        public func setProgressBarVisibiity(_ value:Double) {
            progressBarVisibiity.value = value
        }
    
        public func setErrorMessageVisibiity(_ value:Double) {
            errorMessageVisibiity.value = value
        }
    
        public func setErrorMessageLabel(_ value:String) {
            errorMessageLabel.value = value
        }
    
        public func observeProgressBarVisibiity() -> Observable<Double> {
            return progressBarVisibiity.asObservable().observeOn(MainScheduler())
        }
    
        public func observeErrorMessageVisibiity() -> Observable<Double> {
            return errorMessageVisibiity.asObservable().observeOn(MainScheduler())
        }
    
        public func observeErrorMessageLabel() -> Observable<String> {
            return errorMessageLabel.asObservable().observeOn(MainScheduler())
        }
    }
    
    class ViewController {
        let viewModel = ViewModel()
        let disposeBag = DisposeBag()
        func observeViewModelChanges() {
            viewModel
                .observeProgressBarVisibiity()
                .subscribe(onNext: { value in
                    self.progressBar.hide = viewModel.getProgressBarVisibility()
                })
                .disposed(by: disposeBag)
    
            viewModel
                .observeErrorMessageVisibiity()
                .subscribe(onNext: { value in
                    self.errorMessageLabel.hide = value
                })
                .disposed(by: disposeBag)
    
            viewModel
                .observeErrorMessageLabel()
                .subscribe(onNext: { value in
                    self.errorMessageLabel.text = value
                })
                .disposed(by: disposeBag)
        }
    }
    

    【讨论】:

    • 我不想更新整个 UI 只是为了获取整个 UI 的最新更改,即使单个元素发生更改。那是因为这将使我的 ViewController sneller 并且我将能够从 ViewModel 获取数据并将其传递给 UI。订阅和这类事情既可以发生在 ViewModel 中(对于所有状态),也可以发生在另一个层中,比如 ViewBinder
    • @denis_lor 通过从 viewmodel 订阅 observables,您可以了解视图模型属性的独立变化。这就是上面的代码正在做的事情。
    • 我已经实现了一个看起来像你的解决方案,这就是为什么我想做一些不同的事情。您的解决方案很好,但不是我想要的,谢谢!
    • @denis_lor:您正在寻找某种组合运算符。 merge 非常适合这种情况,但只能与同类型的 Observable 一起使用。我猜你可以在所有 Observable 值之间使用一个通用协议,然后监听变化,但这不是一个理想的方法。您可以查看此问题的选定答案:stackoverflow.com/questions/39050059/…
    • "如果单个 ViewModel 的属性值发生变化,为什么要更新整个 UI?"因为简单。将视图的填充分成更小的部分将打开错误代码,使这些错误更难找到并拥有更多代码。它将您的代码碎片化到尽可能小的水平。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-16
    • 1970-01-01
    • 2021-01-26
    • 1970-01-01
    相关资源
    最近更新 更多