2016-07-14 86 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的水龍頭到可觀察到的,並把它交給視圖模型與沿來自UITextFields的兩個觀察對象。

這是您的方案的一個小工作示例。 (我用一個小權威性客戶端模擬類模擬從服務的響應):

的視圖控制器:

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

這是兩個有趣的部分:

// 1:我們在初始化時將三個Observable注入到ViewModel中。

// 2:然後我們訂閱ViewModel的輸出以在登錄完成後接收Auth模型。

視圖模型:

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

視圖模型得到3個觀測量在其init方法,並將其連接到其輸出:

// 1:聯合登錄文本字段的最新值並將密碼文本字段的最新值組合成一個Observable。

// 2:當用戶按下按鈕時,使用登錄文本字段的最新值和密碼文本字段的最新值,並使用flatMap將其傳遞給身份驗證服務。當身份驗證客戶端返回AuthResponse時,將其映射到Auth模型。設置這個「鏈」的結果爲authResponse輸出的ViewModel

+0

感謝ü這麼多!我非常難以理清它是如何工作的,而你的答案真的幫助了我。 – Marina

+0

您應該儘可能避免使用主題,在這種情況下您可以輕鬆避免使用主題。 –

+0

@DanielT感謝您的評論!你是完全正確的,我改變了我的答案中的例子,使用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 repo中的例子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 
    } 
} 
相關問題