UITextField使用RxSwift绑定到ViewModel

时间:2017-07-21 10:08:41

标签: ios swift mvvm rx-swift

我愿意使用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 { $0 }
            .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)     let _ = viewModel.username.asObservable()。bind(to:passwordTextField.rx.text)

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

第二个实际编译,但不起作用(即viewModel.username不会改变)

主要问题在于我在将参数传递给bindbind(to:方法时盲目拍摄,因为自动完成在这里并不真正有用..我使用的是swift 3和Xcode 8.3。 2。

2 个答案:

答案 0 :(得分:47)

@XFreire是正确的,orEmpty是一个缺失的魔力,但是你可能会发现你的代码看起来更新到最新的语法并纠正错误可能是有益的:

首先是视图模型......

  • Variable类型应始终使用let定义。您不希望替换变量,您只想将新数据合并为一个。
  • 您定义isValid的方式,每次绑定/订阅时都会创建一个新的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 } } } 。在这个简单的情况下,因为你只绑定它一次并不重要,但总的来说,这不是一个好的做法。更好的方法是在构造函数中只使用一次isValid。

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

let

视图控制器。同样,在定义Rx元素时使用addDisposableTo()

  • disposed(by:)已被弃用,而不是使用bindTo()
  • bind(to:)已被弃用,而不是使用map
  • 您不需要viewModel.isValid链中的disposed(by:)
  • 您也错过了该链中的viewModel

在这种情况下,如果在加载后者之前由视图控制器之外的某些内容分配了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.map { $0 } .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 } } 成为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)
}

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

=IF(COUNTIF(B2:G2,">0"),"True","False")

然后你的viewDidLoad看起来像这样:

<form action=mitarbeiterauswahl.php method=post>

<?php 
$select_html = '<select name="mitarbeiter">'; 
$select_list_data = pg_fetch_array($mitarbeiter);  
?>
<?php foreach($select_list_data as $row){  ?>
<?php 
     $select_html .= '<option value="'. $row['id'] .'">('. $row['id'] .') '. $row['vorname'] .' '. $row['nachname'] .'</option>';
 ?> 

<?php } ?>
<?php $select_html .= '</select>'; ?>


<table border=1>
<tr>
    <th>Arbeitspaket-ID</th>
    <th>Arbeitspaketbezeichnung</th>
    <th>Mitarbeiterbedarf</th>
    <th>Mitarbeiterzuordnung</th>
</tr>
<?php

 $fetch_results = pg_fetch_array($arbeitspaket);
 foreach($fetch_results as $results){ ?>          
    <tr>
    <td><?php echo $results['apid']; ?></td>
    <td><?php echo $results['arbeitspaketbezeichnung']; ?></td>
    <td><?php echo $results['mitarbeiterbedarf']; ?></td>
    <td>
        <?php echo $select_html; ?>
    </td>
    </tr>
<?php } ?>
</table>

使用此样式,一般规则是依次考虑每个输出。查找影响该特定输出的所有输入,并编写一个函数,将所有输入作为Observables并将输出生成为Observable。

答案 1 :(得分:14)

您应该添加.orEmpty

试试这个:

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

......对于UITextField的其余部分

也一样

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