Привязка 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)

... и то же самое для остальной части вашей UITextFields

The text свойство является свойством элемента управления типа String?. Добавление orEmpty вы превратите свой String? свойство control в свойство control типа String.