2016-07-14 18 views
7

Ho un controller di autorizzazione con 2 proprietà UITextField e 1 UIButton. Voglio associare la mia vista a ViewModel ma non so come farlo. Questo è il mio AuthorizatioVC.swift:Come associare rx_tap (UIButton) a 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? 

} 

} 

E questo è il mio 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 
} 
} 

risposta

11

è possibile attivare i rubinetti sul UIButton in un osservabile e consegnarlo al ViewModel insieme al due oggetti osservabili dagli UITextFields.

Questo è un piccolo esempio di lavoro per il tuo scenario. (Io ho usato una piccola classe finta auth cliente per simulare la risposta dal servizio):

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

Queste sono le due parti interessanti:

// 1: iniettare i tre osservabili nel ViewModel quando inizializziamo.

// 2: Quindi ci si abbona all'output del ViewModel per ricevere il modello Auth dopo aver effettuato il login.

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

Il ViewModel ottiene i 3 osservabili nel suo metodo init e li collega alla sua uscita:

// 1: Unire l'ultimo valore del campo di testo di login e l'ultimo valore del campo di testo della password in un Osservabile.

// 2: quando l'utente preme il pulsante, utilizzare l'ultimo valore del campo di testo di accesso e l'ultimo valore del campo di testo della password e passarlo al servizio di autenticazione utilizzando flatMap. Quando il client auth restituisce un AuthResponse, mapparlo al modello Auth. Imposta il risultato di questa "catena" come l'uscita authResponse del ViewModel

+0

Thank u così tanto! Ho avuto davvero difficoltà a cercare di capire come funziona e la tua risposta mi ha davvero aiutato. – Marina

+0

Dovresti evitare di usare i soggetti quando puoi, e puoi evitarlo facilmente in questo caso. –

+0

@DanielT Grazie per il tuo impegno! Hai perfettamente ragione, ho cambiato l'esempio nella mia risposta per usare il modo in cui è suggerito nel repository RxSwift. – joern

0

Il problema qui è che stai cercando di rendere il tuo "viewModel" una classe. Dovrebbe essere una funzione.

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

Usa configurarlo in questo modo nella tua viewDidLoad:

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

Se si dispone di uscite multiple per il vostro modello di vista, allora potrebbe essere la pena di fare una classe (invece di restituire un tuple da una funzione.) Se si desidera farlo, quindi GithubSignupViewModel1 dagli esempi nel repository RxSwift è un ottimo esempio di come configurarlo.

2

Primo utilizzo approccio 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 
    }) 
    } 

} 

Il secondo approccio utilizzare 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 
    } 
} 
Problemi correlati