2016-07-14 6 views
7

Ich habe Autorisierungscontroller mit 2 UITextField-Eigenschaften und 1 UIButton. Ich möchte meine Ansicht an ViewModel binden, weiß aber nicht, wie ich es machen soll. Das ist mein AuthorizatioVC.swift:Wie bindet man rx_tap (UIButton) an 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? 

} 

} 

Und das ist mein 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 
} 
} 

Antwort

11

Sie können die Hähne auf dem UIButton in eine beobachtbare und geben es an die Ansichtsmodell zusammen mit der Wende zwei Observables aus den UITextFields.

Dies ist ein kleines Arbeitsbeispiel für Ihr Szenario. (Ich habe eine kleine Auth Client mock Klasse die Antwort vom Dienst zu simulieren):

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

Dies sind die beiden interessantesten Teile:

// 1: Wir injiziere die drei Observablen in das ViewModel, wenn wir es initialisieren.

// 2: Dann abonnieren wir die Ausgabe des ViewModels, um das Modell Auth nach dem Login zu erhalten.

Das Ansichtsmodell:

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

Das Ansichtsmodell erhält die 3 Observable in seiner init-Methode und verbindet sie mit ihrem Ausgang:

// 1: Neueste Wert des Login-Textfeld Kombinat und der letzte Wert des Passwort-Textfelds in einem Observable.

// 2: Wenn der Benutzer die Schaltfläche drückt, verwenden Sie den letzten Wert des Login-Textfelds und den letzten Wert des Passwort-Textfelds und übergeben Sie diesen an den Auth-Dienst mit flatMap. Wenn der Authentifizierungsclient eine AuthResponse zurückgibt, ordnen Sie diese dem Modell Auth zu. Setzen Sie das Ergebnis dieser "Kette" als authResponse Ausgabe des ViewModel

+0

Thank u so much! Es hat mir wirklich schwer gefallen, herauszufinden, wie es funktioniert und deine Antwort hat mir wirklich geholfen. – Marina

+0

Sie sollten es vermeiden, Themen zu verwenden, wenn Sie können, und Sie können es in diesem Fall leicht vermeiden. –

+0

@DanielT Vielen Dank für Sie comment! Sie haben völlig Recht, ich habe das Beispiel in meiner Antwort so geändert, dass es wie im RxSwift-Repo vorgeschlagen wird. – joern

0

Das Problem hier ist, dass Sie versuchen, Ihr "viewModel" eine Klasse zu machen. Es sollte eine Funktion sein.

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

Nutzung setzen Sie ihn, indem Sie diese in Ihrem viewDidLoad up:

let auth = viewModel(loginTxtField.rx_text, passwordTxtField.rx_text, button.rx_tap) 

Wenn Sie mehrere Ausgänge für Ihre Ansicht Modell haben, dann könnte es sich lohnen, eine Klasse zu machen (und nicht als eine Rückkehr Tupel von einer Funktion.) Wenn Sie das tun wollen, dann ist GithubSignupViewModel1 aus den Beispielen in der RxSwift Repo ein hervorragendes Beispiel für die Einrichtung.

2

Erster Ansatz Verwendung 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 
    }) 
    } 

} 

Der zweite Ansatz verwenden 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 
    } 
} 
Verwandte Themen