Ответ 1
@XFreire прав в том, что orEmpty
был отсутствующей магией, но вам может быть orEmpty
посмотреть, как будет выглядеть ваш код, обновленный до последнего синтаксиса, и исправленные ошибки:
Сначала модель представления...
- Типы
Variable
всегда должны быть определены с помощьюlet
. Вы не хотите когда-либо заменять переменную, вы просто хотите вставить новые данные в одну. - Как вы определили свой
isValid
, новый будет создаваться каждый раз, когда вы привязываете/подписываетесь на него. В этом простом случае это не имеет значения, потому что вы привязываетесь к нему только один раз, но в целом это не очень хорошая практика. Лучше сделать isValid наблюдаемым только один раз в конструкторе.
При полном использовании Rx вы обычно обнаружите, что ваши модели представлений состоят из набора let и одного конструктора. Это необычно иметь любые другие методы или даже вычисляемые свойства.
struct LoginViewModel {
let username = Variable<String>("")
let password = Variable<String>("")
let isValid: Observable<Bool>
init() {
isValid = Observable.combineLatest(self.username.asObservable(), self.password.asObservable())
{ (username, password) in
return username.characters.count > 0
&& password.characters.count > 0
}
}
}
И контроллер просмотра. Опять же, используйте let
при определении элементов Rx.
-
addDisposableTo()
устарел из-за предпочтения использованияdisposed(by:)
-
bindTo()
устарел из-за предпочтения использованияbind(to:)
- Вам не нужна
map
в вашей цепочкеviewModel.isValid
. - Вы пропустили
disposed(by:)
в этой цепочке.
В этом случае вы могли бы фактически захотеть, чтобы ваш viewModel
был var, если он назначен чем-то вне контроллера представления, прежде чем последний будет загружен.
class LoginViewController: UIViewController {
var usernameTextField: UITextField!
var passwordTextField: UITextField!
var confirmButton: UIButton!
let viewModel = LoginViewModel()
let disposeBag = DisposeBag()
override func viewDidLoad() {
usernameTextField.rx.text
.orEmpty
.bind(to: viewModel.username)
.disposed(by: disposeBag)
passwordTextField.rx.text
.orEmpty
.bind(to: viewModel.password)
.disposed(by: disposeBag)
//from the viewModel
viewModel.isValid
.bind(to: confirmButton.rx.isEnabled)
.disposed(by: disposeBag)
}
}
Наконец, ваша модель представления может быть заменена одной функцией вместо структуры:
func confirmButtonValid(username: Observable<String>, password: Observable<String>) -> Observable<Bool> {
return Observable.combineLatest(username, password)
{ (username, password) in
return username.characters.count > 0
&& password.characters.count > 0
}
}
Тогда ваш viewDidLoad будет выглядеть так:
override func viewDidLoad() {
super.viewDidLoad()
let username = usernameTextField.rx.text.orEmpty.asObservable()
let password = passwordTextField.rx.text.orEmpty.asObservable()
confirmButtonValid(username: username, password: password)
.bind(to: confirmButton.rx.isEnabled)
.disposed(by: disposeBag)
}
Используя этот стиль, общее правило состоит в том, чтобы рассматривать каждый вывод по очереди. Найдите все входные данные, которые влияют на этот конкретный выход, и напишите функцию, которая принимает все входные данные как наблюдаемые и производит выходные данные как наблюдаемые.