使用 RxSwift 将 UITextField 绑定到 ViewModel

UITextField binding to ViewModel with RxSwift

我愿意将 RxSwift 用于模型值和视图控制器之间的 MVVM 绑定。我想遵循此 realm.io tutorial,但此后绑定显然发生了变化,示例代码无法编译。这是示例代码,我认为我已经修复了最糟糕的拼写错误/遗漏的东西:

LoginViewModel.swift

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.swift

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 { [=11=] }
            .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) 让 _ = viewModel.username.asObservable().bind(to: passwordTextField.rx.text)

let _ = viewModel.username.asObservable()
            .map { [=12=] }
            .bind(to: usernameTextField.rx.text)

第二个实际上编译了,但不起作用(即。viewModel.username 没有改变)

这里的主要问题是我在将参数传递给 bindbind(to: 方法时盲目射击,因为自动完成在这里并不是很有用..我正在使用 swift 3 和 Xcode 8.3.2.

您应该添加 .orEmpty.

试试这个:

usernameTextField.rx.text
    .orEmpty
    .bindTo(self.viewModel. username)
    .addDisposableTo(disposeBag)

... 其余的 UITextFields

也一样

text 属性 是类型 String? 的控件 属性。添加 orEmpty 可以将 String? 控件 属性 转换为 String.

类型的控件 属性

@XFreire 是正确的,orEmpty 是缺失的魔法,但它可能对您有指导意义,看看您的代码看起来像更新到最新的语法和更正的错误:

首先是视图模型...

  • Variable 类型应始终用 let 定义。您不想 替换 一个变量,您只想将新数据推送到一个变量中。
  • 按照您定义 isValid 的方式,每次 bind/subscribe 都会创建一个新的。在这种简单的情况下,这无关紧要,因为您只绑定一次,但一般来说,这不是好的做法。更好的方法是在构造函数中只使 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
        }
    }
}

还有视图控制器。同样,在定义 Rx 元素时使用 let

  • addDisposableTo() 已被弃用,而不是使用 disposed(by:)
  • bindTo() 已被弃用,而不是使用 bind(to:)
  • 您的 viewModel.isValid 链中不需要 map
  • 你也错过了那个链中的 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)
}

使用这种风格,一般规则是依次考虑每个输出。找到影响该特定输出的所有输入,并编写一个函数,将所有输入作为 Observable,并将输出作为 Observable。