モデル値とビューコントローラー間のMVVMバインディングにRxSwiftを使用したいと思います。私はこれに従うことを望んでいました realm.ioチュートリアル ですが、それ以来バインディングは明らかに変化しており、サンプルコードはコンパイルされません。以下にサンプルコードを示します。ここでは、最悪のタイプミス/不足事項を修正したと思います。
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)
_
この2つ目は実際にコンパイルされますが、機能しません(つまり、viewModel.usernameは変更されません)
ここでの主な問題は、bind
メソッドと_bind(to:
_メソッドにパラメーターを渡すときにブラインドを撮影していることです。ここではオートコンプリートはあまり役に立たないためです。Swift 3を使用していますおよびXcode 8.3.2。
@XFreireはorEmpty
が不足している魔法であることは正しいですが、最新の構文に修正されてエラーが修正されたコードがどのように見えるかを知ることは有益かもしれません:
最初にビューモデル...
Variable
型は常にlet
で定義する必要があります。 replace変数ではなく、新しいデータを1つにプッシュするだけです。isValid
を定義する方法は、バインドまたはサブスクライブするたびに新しいものが作成されます。この単純なケースでは、バインドするのは一度だけなので重要ではありませんが、一般的にはこれは良い習慣ではありません。コンストラクタで一度だけisValidを観測可能にするのが良いでしょう。Rxを完全に使用する場合、通常、ビューモデルは一連のLetと1つのコンストラクターで構成されます。他のメソッドや計算されたプロパティさえ持っていることは珍しいです。
_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
}
}
}
_
そして、View Controller。繰り返しますが、Rx要素を定義するときはlet
を使用します。
addDisposableTo()
は、disposed(by:)
の使用よりも推奨されていませんbindTo()
は、bind(to:)
の使用よりも推奨されていませんviewModel.isValid
_チェーンにmap
は必要ありません。disposed(by:)
がありませんでした。この場合、View Controllerがロードされる前にView Controllerの外部の何かによって割り当てられている場合、実際に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として生成する関数を記述します。
.orEmpty
を追加する必要があります。
これを試して:
usernameTextField.rx.text
.orEmpty
.bindTo(self.viewModel. username)
.addDisposableTo(disposeBag)
... UITextField
sの残りについても同じ
text
プロパティは、String?
型のコントロールプロパティです。 orEmpty
を追加すると、String?
コントロールプロパティをString
型のコントロールプロパティに変換します。