ios 使用 RxSwift 将 UITextField 绑定到 ViewModel

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/45234915/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 09:20:55  来源:igfitidea点击:

UITextField binding to ViewModel with RxSwift

iosswiftmvvmrx-swift

提问by satellink

I am willing to use RxSwift for MVVM binding between model values & view controllers. I wanted to follow this realm.io tutorial, but the binding has apparently changed since then, and the sample code does not compile. Here is the sample code, where I think I've fixed the worst typos / missing things:

我愿意使用 RxSwift 在模型值和视图控制器之间进行 MVVM 绑定。我想遵循这个realm.io教程,但从那时起绑定显然发生了变化,并且示例代码无法编译。这是示例代码,我认为我已经修复了最糟糕的拼写错误/遗漏的东西:

LoginViewModel.swift

登录ViewModel.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

登录ViewController.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 { 
let _ = viewModel.username.asObservable()
            .map { 
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
        }
    }
}
} .bind(to: usernameTextField.rx.text)
} .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>)'

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

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

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 bindand bind(to:methods, since the autocompletion is not really helpful here.. I am using swift 3 and Xcode 8.3.2.

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

回答by Daniel T.

@XFreire is right that orEmptywas 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是缺少的魔法,但它可能对您有指导意义,看看您的代码更新为最新语法并更正错误的样子:

First the view model...

首先是视图模型...

  • Variabletypes should always be defined with let. You don't want to ever replacea Variable, you just want to push new data into one.
  • The way you have your isValiddefined, a new one would be created every time you bind/subscribe to it. 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.
  • Variable类型应始终用let. 你不想替换一个变量,你只想将新数据推入一个。
  • 按照您isValid定义的方式,每次绑定/订阅它时都会创建一个新的。在这种简单的情况下,这无关紧要,因为您只绑定到它一次,但总的来说,这不是一个好的做法。更好的是在构造函数中仅使 isValid 可观察一次。

When using Rx fully, you will usually find that your view models consist of a bunch of let's and a single constructor. It's unusual to have any other methods or even computed properties.

当完全使用 Rx 时,您通常会发现您的视图模型由一堆 let's 和一个构造函数组成。有任何其他方法甚至计算属性是不寻常的。

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

And the view controller. Again, use letwhen defining Rx elements.

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

  • addDisposableTo()has been deprecated in preference to using disposed(by:)
  • bindTo()has been deprecated in preference to using bind(to:)
  • You don't need the mapin your viewModel.isValidchain.
  • You were missing the disposed(by:)in that chain as well.
  • addDisposableTo()已被弃用,而不是使用 disposed(by:)
  • bindTo()已被弃用,而不是使用 bind(to:)
  • 你并不需要map在你的viewModel.isValid链条。
  • 您也错过了disposed(by:)该链中的 。

In this case, you might actually want your viewModelto be a var if it is assigned by something outside the view controller before the latter is loaded.

在这种情况下,viewModel如果在加载视图控制器之前由视图控制器之外的某些东西分配它,您实际上可能希望您成为一个 var。

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

Lastly, your view model could be replaced by a single function instead of a struct:

最后,您的视图模型可以由单个函数而不是结构替换:

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

Then your viewDidLoad would look like this:

然后您的 viewDidLoad 将如下所示:

##代码##

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.

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

回答by XFreire

You should add .orEmpty.

你应该添加.orEmpty.

Try this:

尝试这个:

##代码##

... and the same for the rest of your UITextFields

... 其余UITextField的也一样

The textproperty is a control property of type String?. Adding orEmptyyou transform your String?control property into control property of type String.

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