如何将 rx_tap (UIButton) 绑定到 ViewModel?
How to bind rx_tap (UIButton) to ViewModel?
我有带有 2 个 UITextField 属性和 1 个 UIButton 的授权控制器。我想将我的 View 绑定到 ViewModel 但不知道该怎么做。
这是我的 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?
}
}
这是我的 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
}
}
您可以将 UIButton 上的点击变成一个 Observable,并将它与来自 UITextFields 的两个 Observable 一起交给 ViewModel。
这是针对您的方案的一个小型工作示例。 (我使用了一个小型 auth 客户端 mock class 来模拟来自服务的响应):
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:我们在初始化的时候将三个Observable注入到ViewModel中
// 2: 然后我们订阅 ViewModel 的输出以在完成登录后接收 Auth
模型。
视图模型:
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 在其 init 方法中获取 3 个 Observable 并将它们连接到其输出:
// 1: 将登录文本字段的最新值和密码文本字段的最新值合并为一个 Observable。
// 2: 当用户按下按钮时,使用登录文本字段的最新值和密码文本字段的最新值,并使用 flatMap
将其传递给 auth 服务。当 auth 客户端 returns a AuthResponse
时,将其映射到 Auth
模型。将此 "chain" 的结果设置为 ViewModel
的 authResponse
输出
这里的问题是您试图让您的 "viewModel" 变成 class。它应该是一个函数。
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: [=10=].token) }
通过在您的 viewDidLoad 中执行此操作来使用设置:
let auth = viewModel(loginTxtField.rx_text, passwordTxtField.rx_text, button.rx_tap)
如果您的视图模型有多个输出,那么制作一个 class 可能是值得的(而不是从函数返回一个元组。)如果您想这样做,那么 GithubSignupViewModel1
来自 RxSwift 存储库中的示例是一个很好的设置示例。
第一种方法使用 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
}
}
我有带有 2 个 UITextField 属性和 1 个 UIButton 的授权控制器。我想将我的 View 绑定到 ViewModel 但不知道该怎么做。 这是我的 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?
}
}
这是我的 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
}
}
您可以将 UIButton 上的点击变成一个 Observable,并将它与来自 UITextFields 的两个 Observable 一起交给 ViewModel。
这是针对您的方案的一个小型工作示例。 (我使用了一个小型 auth 客户端 mock class 来模拟来自服务的响应):
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:我们在初始化的时候将三个Observable注入到ViewModel中
// 2: 然后我们订阅 ViewModel 的输出以在完成登录后接收 Auth
模型。
视图模型:
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 在其 init 方法中获取 3 个 Observable 并将它们连接到其输出:
// 1: 将登录文本字段的最新值和密码文本字段的最新值合并为一个 Observable。
// 2: 当用户按下按钮时,使用登录文本字段的最新值和密码文本字段的最新值,并使用 flatMap
将其传递给 auth 服务。当 auth 客户端 returns a AuthResponse
时,将其映射到 Auth
模型。将此 "chain" 的结果设置为 ViewModel
authResponse
输出
这里的问题是您试图让您的 "viewModel" 变成 class。它应该是一个函数。
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: [=10=].token) }
通过在您的 viewDidLoad 中执行此操作来使用设置:
let auth = viewModel(loginTxtField.rx_text, passwordTxtField.rx_text, button.rx_tap)
如果您的视图模型有多个输出,那么制作一个 class 可能是值得的(而不是从函数返回一个元组。)如果您想这样做,那么 GithubSignupViewModel1
来自 RxSwift 存储库中的示例是一个很好的设置示例。
第一种方法使用 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
}
}