How to associate rx_tap (UIButton) with ViewModel?

I have an authorization controller with 2 UITextField and 1 UIButton properties. I want to bind my View to the ViewModel, but I don’t know how to do it. This is my 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? } } 

And this is my 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 } } 
+7
ios swift rx-swift
source share
3 answers

You can turn the taps on the UIButton in the Observable and pass it to the ViewModel along with two observables from the UITextFields.

This is a small working example for your scenario. (I used a small mk client class to simulate a service response):

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

These are two interesting parts:

// 1: We insert three Observables into the ViewModel when we initialize it.

// 2: Then we subscribe to the ViewModel output to get the Auth model after the login has been completed.

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 receives 3 Observables in its init method and hooks them to its output:

// 1: Combine the last value of the input text field and the last value of the password text field in one observation.

// 2: When the user clicks the button, use the last value of the text input field and the last value of the password text field and pass this to the auth service using flatMap . When the auth client returns AuthResponse , map this to the Auth model. Set the result of this chain as the output of the AuthResponse ViewModel

+11
source share

The first approach uses 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 }) } } 

The second approach uses 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 } } 
+2
source share

The problem is that you are trying to make your viewModel class. It must be a function.

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

Use it by doing this in your DidLoad view:

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

If you have several exits for your view model, it might be worth making a class (instead of returning a tuple from a function.) If you want to do this, then the GithubSignupViewModel1 from the examples in the RxSwift repo is a great example of how to set it up.

0
source share

All Articles