Как привязать 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-отличный пример того, как его настроить.