How to make ViewController observe any changes to ViewModel variables (Variable<..>) with RxSwift?

喜欢而已 提交于 2019-12-11 08:14:47

问题


How can I observe changes to Variable<...> value (RxSwift Variable) inside the ViewModel class from the ViewController?

So in case the value of any of my Variable<..> that I have in the ViewModel changes within the things happening in the ViewModel then the ViewController will be noticed "Hey! One or more Variable<..> in the ViewModel changed! Ask the ViewModel for the data you need to update the UI and update the UI!"

And then the ViewController call a method updateUI() inside the ViewController and within it it asks the ViewModel for all the info like status/state to update the UI something like:

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

回答1:


Why do you want to update the complete UI if the value of a single ViewModel's property changes?

RxSwift enables you to listen to changes independently and you can react/change UI accordingly.

In my view, this is how your ViewModel and ViewController classes should look:

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)
    }
}



回答2:


To update your UI I suggest to use a viewState variable that you can update, when needed, in your view model class, for example:

/// 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)
}

Then in your viewModel class:

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

Where YourViewControllerState is an enum with your specific cases:

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

And finally in your 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()
    }
}



回答3:


We can construct a two-way binding operator which you can just use bindTo. Here are implementations for ControlProperty <-> Variable and Variable <-> Variable:

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)
}

You can find a detailed answer to your question in the following post. Two way binding in RxSwift



来源:https://stackoverflow.com/questions/53479087/how-to-make-viewcontroller-observe-any-changes-to-viewmodel-variables-variable

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!