2つのUITextFieldプロパティと1つのUIButtonを備えた認証コントローラーがあります。 ViewをViewModelにバインドしたいのですが、その方法がわかりません。これは私のAuthorizatioVC.Swiftです:
class AuthorizationViewController: UIViewController {
let disposeBag = DisposeBag()
@IBOutlet weak var passwordTxtField: UITextField!
@IBOutlet weak var loginTxtField: UITextField!
@IBOutlet weak var button: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
addBindsToViewModel()
}
func addBindsToViewModel(){
let authModel = AuthorizationViewModel(authClient: AuthClient())
authModel.login.asObservable().bindTo(passwordTxtField.rx_text).addDisposableTo(self.disposeBag)
authModel.password.asObservable().bindTo(loginTxtField.rx_text).addDisposableTo(self.disposeBag)
//HOW TO BIND button.rx_tap here?
}
}
そしてこれは私のAuthorizationViewModel.Swiftです:
final class AuthorizationViewModel{
private let disposeBag = DisposeBag()
//input
//HOW TO DEFINE THE PROPERTY WHICH WILL BE BINDED TO RX_TAP FROM THE BUTTON IN VIEW???
let authEvent = ???
let login = Variable<String>("")
let password = Variable<String>("")
//output
private let authModel: Observable<Auth>
init(authClient: AuthClient){
let authModel = authEvent.asObservable()
.flatMap({ (v) -> Observable<Auth> in
return authClient.authObservable(String(self.login.value), mergedHash: String(self.password.value))
.map({ (authResponse) -> Auth in
return self.convertAuthResponseToAuthModel(authResponse)
})
})
}
func convertAuthResponseToAuthModel(authResponse: AuthResponse) -> Auth{
var authModel = Auth()
authModel.token = authResponse.token
return authModel
}
}
UIButtonのタップをObservableに変えて、UITextFieldsからの2つのObservableと一緒にViewModelに渡すことができます。
これは、シナリオの小さな実用的な例です。 (私は小さな認証クライアントのモッククラスを使用して、サービスからの応答をシミュレートしました):
ViewController:
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let loginTxtField = UITextField(frame: CGRect(x: 20, y: 50, width: 200, height: 40))
let passwordTxtField = UITextField(frame: CGRect(x: 20, y: 110, width: 200, height: 40))
let loginButton = UIButton(type: .RoundedRect)
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(red: 0.9, green: 0.9, blue: 0.9, alpha: 1)
loginTxtField.backgroundColor = UIColor.whiteColor()
view.addSubview(loginTxtField)
passwordTxtField.backgroundColor = UIColor.whiteColor()
view.addSubview(passwordTxtField)
loginButton.setTitle("Login", forState: .Normal)
loginButton.backgroundColor = UIColor.whiteColor()
loginButton.frame = CGRect(x: 20, y: 200, width: 200, height: 40)
view.addSubview(loginButton)
// 1
let viewModel = ViewModel(
withLogin: loginTxtField.rx_text.asObservable(),
password: passwordTxtField.rx_text.asObservable(),
didPressButton: loginButton.rx_tap.asObservable()
)
// 2
viewModel.authResponse
.subscribeNext { response in
print(response)
}
.addDisposableTo(disposeBag)
}
}
これらは2つの興味深い部分です:
// 1:初期化するときに3つのObservableをViewModelに挿入します。
// 2:次に、ログインが完了した後、ViewModelの出力をサブスクライブして、Auth
モデルを受け取ります。
ViewModel:
import RxSwift
struct Auth {
let token: String
}
struct AuthResponse {
let token: String
}
class ViewModel {
// Output
let authResponse: Observable<Auth>
init(withLogin login: Observable<String>, password: Observable<String>, didPressButton: Observable<Void>) {
let mockAuthService = MockAuthService()
// 1
let userInputs = Observable.combineLatest(login, password) { (login, password) -> (String, String) in
return (login, password)
}
// 2
authResponse = didPressButton
.withLatestFrom(userInputs)
.flatMap { (login, password) in
return mockAuthService.getAuthToken(withLogin: login, mergedHash: password)
}
.map { authResponse in
return Auth(token: authResponse.token)
}
}
}
class MockAuthService {
func getAuthToken(withLogin login: String, mergedHash: String) -> Observable<AuthResponse> {
let dummyAuthResponse = AuthResponse(token: "dummyToken->login:\(login), password:\(mergedHash)")
return Observable.just(dummyAuthResponse)
}
}
ViewModelは、initメソッドで3つのObservableを取得し、それらを出力に接続します。
// 1:ログインテキストフィールドの最新値とパスワードテキストフィールドの最新値を1つのObservableに結合します。
// 2:ユーザーがボタンを押したら、ログインテキストフィールドの最新値とパスワードテキストフィールドの最新値を使用し、flatMap
を使用して認証サービスに渡します。認証クライアントがAuthResponse
を返したら、それをAuth
モデルにマップします。この「チェーン」の結果をauthResponse
のViewModel
出力として設定します
最初のアプローチの使用PublishSubject
class ViewController: UIViewController {
@IBOutlet weak var loginBtn: UIButton!
var vm: ViewModel?
let disposebag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
bindUi()
}
func bindUi() {
(loginBtn.rx.tap).bind(to: vm!.loginSbj).addDisposableTo(disposebag)
}
}
class ViewModel {
let loginSbj = PublishSubject<Void>()
init() {
loginSbj.do(onNext: { _ in
// do something
})
}
}
2番目のアプローチは アクション を使用します
class ViewController: UIViewController {
@IBOutlet weak var loginBtn: UIButton!
var vm: ViewModel?
override func viewDidLoad() {
super.viewDidLoad()
bindUi()
}
func bindUi() {
loginBtn.rx.action = vm!.loginAction
}
}
class ViewModel {
let loginAction: CococaAction<Void, Void> = CocoaAction {
// do something
}
}
ここでの問題は、「viewModel」をクラスにしようとしていることです。それは関数でなければなりません。
func viewModel(username: Observable<String>, password: Observable<String>, button: Observable<Void>) -> Observable<Auth> {
return button
.withLatestFrom(Observable.combineLatest(login, password) { (login, password) })
.flatMap { login, password in
server.getAuthToken(withLogin: login, password: password)
}
.map { Auth(token: $0.token) }
ViewDidLoadでこれを実行してセットアップを使用します。
let auth = viewModel(loginTxtField.rx_text, passwordTxtField.rx_text, button.rx_tap)
ビューモデルに複数の出力がある場合は、(関数からタプルを返すのではなく)クラスを作成する価値があるかもしれません。それを実行したい場合は、GithubSignupViewModel1
RxSwiftリポジトリの例から、それを設定する方法の優れた例です。