2016-07-14 2 views
7

У меня есть контроллер авторизации с 2 свойствами UITextField и 1 UIButton. Я хочу привязать свой View к 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 вместе с две наблюдаемые из UITextFields.

Это небольшой рабочий пример для вашего сценария. (Я использовал небольшой аутентификации клиента макет класса для имитации ответа от службы):

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

Эти две интересные детали:

// 1: Мы вставляем три Observables в ViewModel, когда мы его инициализируем.

// 2: Затем мы подписываемся на вывод ViewModel для получения модели Auth после того, как логин был выполнен.

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 получает 3 Наблюдаемые в методе инициализации и соединяет их к выходу:

// 1: зерноуборочный последнее значение текстового поля авторизации и последнее значение текстового поля пароля в одно наблюдение.

// 2: Когда пользователь нажимает кнопку, используйте последнее значение текстового поля для входа и самое последнее значение текстового поля пароля и передайте его службе auth с помощью flatMap. Когда клиент auth возвращает AuthResponse, сопоставьте его с моделью Auth. Задайте результат этой «цепочки» как вывод authResponseViewModel

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

Если у вас есть несколько выходов для вашей модели представления, то это может быть стоит сделать класс (а не возвращая кортеж из функции.) Если вы хотите это сделать, то GithubSignupViewModel1 из примеров в репозитории RxSwift - отличный пример того, как его настроить.

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 
    } 
} 
Смежные вопросы