简体   繁体   English

UITextField使用RxSwift绑定到ViewModel

[英]UITextField binding to ViewModel with RxSwift

I am willing to use RxSwift for MVVM binding between model values & view controllers. 我愿意使用RxSwift在模型值和视图控制器之间进行MVVM绑定。 I wanted to follow this realm.io tutorial , but the binding has apparently changed since then, and the sample code does not compile. 我想按照这个realm.io教程 ,但从那时起绑定显然已经改变,示例代码不能编译。 Here is the sample code, where I think I've fixed the worst typos / missing things: 这是示例代码,我认为我已经修复了最糟糕的拼写错误/丢失的东西:

LoginViewModel.swift 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 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 { $0 }
            .bindTo(confirmButton.rx.isEnabled)
    }
}

The controller bindings do not compile. 控制器绑定不编译。 It is pretty close to impossible to track the correct way to do these, as the RxSwift documentation is pretty unhelpful, and the Xcode autocompletion does not suggest anything useful. 跟踪正确的方法是非常接近的,因为RxSwift文档非常无用,并且Xcode自动完成没有提示任何有用的东西。

The first issue is with this binding, which does not compile: usernameTextField.rx.text.bindTo(viewModel.username).addTo(disposeBag) 第一个问题是这个绑定,它不编译: usernameTextField.rx.text.bindTo(viewModel.username).addTo(disposeBag)

The error: 错误:

LoginViewController.swift:15:35: Cannot invoke 'bindTo' with an argument list of type '(Variable<String>)'

I've tried the following without luck: 我没试过就试过以下内容:

1) usernameTextField.rx.text.bind(to: viewModel.username).addTo(disposeBag) - The error still persists: LoginViewController.swift:15:35: Cannot invoke 'bind' with an argument list of type '(to: 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) let _ = viewModel.username.asObservable().bind(to: passwordTextField.rx.text) 2)let _ = viewModel.username.asObservable()。bind(to:passwordTextField.rx.text)

let _ = viewModel.username.asObservable()
            .map { $0 }
            .bind(to: usernameTextField.rx.text)

This second one actually compiles, but does not work (ie. viewModel.username does not change) 第二个实际编译,但不起作用(即viewModel.username不会更改)

The main problem is here that I am shooting blind when passing parameters to the bind and bind(to: methods, since the autocompletion is not really helpful here.. I am using swift 3 and Xcode 8.3.2. 主要的问题是,我在将参数传递给bindbind(to:时被盲目bind(to:方法,因为自动完成在这里并不真正有用..我使用的是swift 3和Xcode 8.3.2。

@XFreire is right that orEmpty was the missing magic, but it might be instructive for you to see what your code would look like updated to the latest syntax and errors corrected: @XFreire是对的, orEmpty是缺失的魔法,但是你可以看到你的代码看起来像更新到最新的语法并纠正错误可能是orEmpty

First the view model... 首先是视图模型......

  • Variable types should always be defined with let . 应始终使用let定义Variable类型。 You don't want to ever replace a Variable, you just want to push new data into one. 您不想替换变量,只想将新数据合并为一个。
  • The way you have your isValid defined, a new one would be created every time you bind/subscribe to it. 定义isValid的方式,每次绑定/订阅它时都会创建一个新的。 In this simple case that doesn't matter because you only bind to it once, but in general, this is not good practice. 在这个简单的情况下无关紧要,因为你只绑定了一次,但总的来说,这不是一个好的做法。 Better is to make the isValid observable just once in the constructor. 更好的方法是在构造函数中只使用一次isValid。

When using Rx fully, you will usually find that your view models consist of a bunch of let's and a single constructor. 完全使用Rx时,通常会发现视图模型由一堆let和一个构造函数组成。 It's unusual to have any other methods or even computed properties. 拥有任何其他方法甚至计算属性都是不寻常的。

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
        }
    }
}

And the view controller. 和视图控制器。 Again, use let when defining Rx elements. 同样,在定义Rx元素时使用let

  • addDisposableTo() has been deprecated in preference to using disposed(by:) addDisposableTo()已被弃用,优先于使用addDisposableTo() disposed(by:)
  • bindTo() has been deprecated in preference to using bind(to:) bindTo()已被弃用,优先于使用bind(to:)
  • You don't need the map in your viewModel.isValid chain. 您不需要viewModel.isValid链中的map
  • You were missing the disposed(by:) in that chain as well. 你错过了那条链中的disposed(by:)

In this case, you might actually want your viewModel to be a var if it is assigned by something outside the view controller before the latter is loaded. 在这种情况下,如果在加载后者之前由视图控制器之外的某些内容分配了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)
    }
}

Lastly, your view model could be replaced by a single function instead of a struct: 最后,您的视图模型可以替换为单个函数而不是结构:

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
    }
}

Then your viewDidLoad would look like this: 然后你的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)
}

Using this style, the general rule is to consider each output in turn. 使用此样式,一般规则是依次考虑每个输出。 Find all the inputs that influence that particular output and write a function that takes all the inputs as Observables and produces the output as an Observable. 查找影响该特定输出的所有输入,并编写一个函数,将所有输入作为Observables并将输出生成为Observable。

You should add .orEmpty . 你应该添加.orEmpty

Try this: 尝试这个:

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

... and the same for the rest of your UITextField s ...和其余的UITextField相同

The text property is a control property of type String? text属性是String?类型的控件属性String? . Adding orEmpty you transform your String? 添加orEmpty你转换你的String? control property into control property of type String . 将属性控制为String类型的控件属性。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM