2016-07-14 11 views
7

私は2つのUITextFieldプロパティと1つのUIButtonを持つ承認コントローラを持っています。ビューをViewModelにバインドしたいが、それを行う方法がわからない。 これは私のAuthorizatioVC.swiftです:rx_tap(UIButton)をViewModelにバインドするにはどうすればいいですか?

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

答えて

11

あなたが観察可能にUIButtonのタップをオンにし、一緒にのViewModelに渡すことができます2つのObservableをUITextFieldsから取得します。

このシナリオでは、小さな作業例です。 (私は、サービスからの応答をシミュレートするために、小さな認証クライアントモッククラスを使用):

のViewController:

// 1:我々

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つの興味深い部分です3つのObservableを初期化するときにViewModelに挿入します。

// 2:次に、ログインが完了した後にAuthモデルを受け取るために、ViewModelの出力にサブスクライブします。

の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つの観測を取得し、その出力に接続します。

// 1:ログインテキストフィールドの最新の値を組み合わせますパスワードテキストフィールドの最新の値を1つのObservableに格納します。

// 2:ユーザーがボタンを押すと、ログインテキストフィールドの最新の値とパスワードテキストフィールドの最新値を使用して、flatMapを使用して認証サービスに渡します。認証クライアントがAuthResponseを返す場合は、Authモデルにマッピングします。この「連鎖」の結果をの出力として設定します。

+0

そんなにありがとう!私はそれがどのように動作するかを整理しようとするのは本当に苦労し、あなたの答えは本当に私を助けました。 – Marina

+0

可能なときには主題を使用しないでください。この場合は簡単に避けることができます。 –

+0

@ダニエルあなたに感謝します!あなたはまったく正しいです、私はRxSwiftレポで示唆されているように、私の答えの例を変えて使い方を変えました。 – joern

0

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

あなたがそれをしたいならば、RxSwiftレポの例からのGithubSignupViewModel1は、それを設定するための優れた例です。

2

まずアプローチを使用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 
    }) 
    } 

} 

第二のアプローチを使用Action

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 
    } 
} 
関連する問題