问题
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