J'ai un contrôleur d'autorisation avec 2 propriétés UITextField et 1 UIButton. Je souhaite lier ma vue à ViewModel mais je ne sais pas comment le faire. C'est mon 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?
}
}
Et voici mon 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
}
}
Vous pouvez transformer les robinets de l'UIButton en observable et le remettre au ViewModel avec les deux observables de UITextFields.
Ceci est un petit exemple de travail pour votre scénario. (J'ai utilisé une petite classe fictive du client autorisé pour simuler la réponse du service):
Le 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)
}
}
Ce sont les deux parties intéressantes:
// 1: Nous injectons les trois observables dans le ViewModel lorsque nous l'initialisons.
// 2: Ensuite, nous nous abonnons à la sortie du ViewModel pour recevoir le modèle Auth
une fois la connexion établie.
Le 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 obtient les 3 observables dans sa méthode init et les connecte à sa sortie:
// 1: Combinez la dernière valeur du champ de texte de connexion et la dernière valeur du champ de mot de passe en un seul observable.
// 2: lorsque l'utilisateur appuie sur le bouton, utilisez la dernière valeur du champ de texte de connexion et la dernière valeur du champ de mot de passe et transmettez-la au service d'autorisation à l'aide de flatMap
. Lorsque le client d'authentification renvoie AuthResponse
, mappez-le au modèle Auth
. Définissez le résultat de cette "chaîne" comme sortie authResponse
de la ViewModel
Première approche utiliser 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
})
}
}
La seconde approche utilise 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
}
}
Le problème ici est que vous essayez de faire de votre "viewModel" une classe. Cela devrait être une fonction.
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) }
Utilisez set it up en faisant ceci dans votre viewDidLoad:
let auth = viewModel(loginTxtField.rx_text, passwordTxtField.rx_text, button.rx_tap)
Si vous avez plusieurs sorties pour votre modèle de vue, il peut être intéressant de créer une classe (plutôt que de retourner un tuple à partir d'une fonction.) Si vous voulez le faire, alors GithubSignupViewModel1
à partir des exemples du référentiel RxSwift est un excellent exemple de la façon de le configurer.