Как привязать RX tap (UIButton) к ViewModel?
у меня есть контроллер авторизации с 2 свойствами UITextField и 1 UIButton. Я хочу привязать свой взгляд к ViewModel, но не знаю, как это сделать. Это мой AuthorizatioVC.Свифт:
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.Свифт:
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
}
}
            3 ответов
вы можете превратить краны на UIButton в наблюдаемый и передать его ViewModel вместе с двумя наблюдаемыми из UITextFields.
это небольшой рабочий пример для вашего сценария. (Я использовал небольшой класс макета клиента auth для имитации ответа от службы):
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: мы вводим три наблюдаемых в 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 наблюдаемых в своем методе init и подключает их к своему выходу:
/ / 1: объедините последнее значение текстового поля login и последнее значение текстового поля password в одно наблюдаемое.
/ / 2: когда потребитель отжимает используйте последнее значение текстового поля login и последнее значение текстового поля password и передайте это Службе auth с помощью flatMap. Когда клиент auth возвращает AuthResponse, карте, что до Auth модель. Установите результат этой "цепочки" как authResponse выход ViewModel
первый подход 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
    })
  }
}
второй подход действие
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
  }
}
проблема здесь в том, что вы пытаетесь сделать свой "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: .token) }
используйте настроить его, сделав это в viewDidLoad:
let auth = viewModel(loginTxtField.rx_text, passwordTxtField.rx_text, button.rx_tap)
Если у вас есть несколько выходов для вашей модели представления, то, возможно, стоит сделать класс (а не возвращать кортеж из функции.) Если вы хотите это сделать, то GithubSignupViewModel1 из примеров в репо RxSwift-отличный пример того, как его настроить.