Привязка UITextField к ViewModel с помощью RxSwift
Я готов использовать RxSwift для привязки MVVM между значениями модели и контроллерами представления. Я хотел следовать этому realm.IO учебник, но привязка уже, видимо, изменилась с тех пор, и пример кода не компилируется. Вот пример кода, где я думаю, что исправил худшие опечатки / недостающие вещи:
LoginViewModel.Свифт!--12-->
import RxSwift
struct LoginViewModel {
var username = Variable<String>("")
var password = Variable<String>("")
var isValid : Observable<Bool>{
return Observable.combineLatest(self.username.asObservable(), self.password.asObservable())
{ (username, password) in
return username.characters.count > 0
&& password.characters.count > 0
}
}
}
LoginViewController.Свифт!--12-->
import RxSwift
import RxCocoa
import UIKit
class LoginViewController: UIViewController {
var usernameTextField: UITextField!
var passwordTextField: UITextField!
var confirmButton: UIButton!
var viewModel = LoginViewModel()
var disposeBag = DisposeBag()
override func viewDidLoad() {
usernameTextField.rx.text.bindTo(viewModel.username).addTo(disposeBag)
passwordTextField.rx.text.bindTo(viewModel.password).addTo(disposeBag)
//from the viewModel
viewModel.rx.isValid.map { }
.bindTo(confirmButton.rx.isEnabled)
}
}
привязки контроллера не компиляции. Это довольно близко к невозможно отследить правильный способ сделать это, так как документация RxSwift довольно бесполезна, и автозаполнение Xcode не предлагает ничего полезного.
первая проблема связана с этой привязкой, которая не компилируется:
usernameTextField.rx.text.bindTo(viewModel.username).addTo(disposeBag)
ошибки:
LoginViewController.swift:15:35: Cannot invoke 'bindTo' with an argument list of type '(Variable<String>)'
Я пробовал следующее без везения:
1) usernameTextField.rx.text.bind(to: viewModel.username).addTo(disposeBag)
- ошибка все еще сохраняется:
LoginViewController.swift:15:35: Cannot invoke 'bind' with an argument list of type '(to: Variable<String>)'
2) позволять= _ модель представления.имя пользователя:.asObservable ().bind (to: passwordTextField.rx.текст)
let _ = viewModel.username.asObservable()
.map { }
.bind(to: usernameTextField.rx.text)
этот второй фактически компилируется, но не работает (т. е. модель представления.имя пользователя не меняется)
основная проблема здесь в том, что я стреляю вслепую при передаче параметров в bind
и bind(to:
методы, так как автозаполнение здесь не очень полезно.. Я использую swift 3 и Xcode 8.3.2.
2 ответов
@XFreire прав, что orEmpty
отсутствовала магия, но вам может быть полезно посмотреть, как ваш код будет выглядеть обновленным до последнего синтаксиса и исправленных ошибок:
сначала модель представления...
-
Variable
типы всегда должны определяться с помощьюlet
. Ты никогда не захочешь!--23-->заменить переменная, вы просто хотите, чтобы подтолкнуть новые данные в один. - то, как у вас есть
isValid
новый будет создается каждый раз, когда вы привязываете / подписываетесь на него. В этом простом случае это не имеет значения, потому что вы привязываетесь к нему только один раз, но в целом это не хорошая практика. Лучше сделать isValid наблюдаемым только один раз в конструкторе.
при полном использовании Rx вы обычно обнаружите, что ваши модели представления состоят из группы let'S и одного конструктора. Необычно иметь какие-либо другие методы или даже вычисляемые свойства.
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
в своем } .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) }
используя этот стиль, общее правило состоит в том, чтобы рассмотреть каждый вывод по очереди. Найдите все входы, которые влияют на этот конкретный выход, и напишите функцию, которая принимает все входы как наблюдаемые и производит выход как наблюдаемый.
вы должны добавить .orEmpty
.
попробуйте это:
usernameTextField.rx.text
.orEmpty
.bindTo(self.viewModel. username)
.addDisposableTo(disposeBag)
... и то же самое для остальной части вашей UITextField
s
The text
свойство является свойством элемента управления типа String?
. Добавление orEmpty
вы превратите свой String?
свойство control в свойство control типа String
.