Ios 如何将rx_点击(UIButton)绑定到ViewModel?

Ios 如何将rx_点击(UIButton)绑定到ViewModel?,ios,swift,rx-swift,Ios,Swift,Rx Swift,我有2个UITextField属性和1个UIButton的授权控制器。我想将视图绑定到ViewModel,但不知道如何执行。 这是我的授权书。斯威夫特: class AuthorizationViewController: UIViewController { let disposeBag = DisposeBag() @IBOutlet weak var passwordTxtField: UITextField! @IBOutlet weak var loginTxtField: UIT

我有2个UITextField属性和1个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?

}

}
这是我的授权ViewModel.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
}
}
final class AuthorizationViewModel{
私有出租dispebag=dispebag()
//输入
//如何定义从视图中的按钮绑定到RX_TAP的属性???
让authEvent=???
让login=Variable(“”)
让密码=变量(“”)
//输出
私有模型:可观察
init(authClient:authClient){
让authModel=authEvent.asObservable()
.flatMap({(v)->在
返回authClient.authObservable(String(self.login.value),mergedHash:String(self.password.value))
.map({(authResponse)->Auth in
返回self.convertAuthResponseToAuthModel(authResponse)
})
})
}
func convertAuthResponseToAuthModel(authResponse:authResponse)->Auth{
var authModel=Auth()
authModel.token=authResponse.token
返回模型
}
}

您可以将UIButton上的点击转换为可观察对象,并将其与UITextFields中的两个可观察对象一起交给ViewModel

这是一个适用于您的场景的小示例。(我使用了一个小的auth客户端模拟类来模拟来自服务的响应):

视图控制器:

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)
    }
}
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)
    }
}
以下是两个有趣的部分:

//1:初始化ViewModel时,我们将三个可观察对象注入其中

//2:然后我们订阅ViewModel的输出,在登录完成后接收
Auth
模型

视图模型:

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)
    }
}
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)
    }
}
导入RxSwift
结构验证{
让令牌:字符串
}
结构AuthResponse{
让令牌:字符串
}
类视图模型{
//输出
让我们来回答:可观察的
初始化(使用登录:可观察,密码:可观察,按按钮:可观察){
让mockAuthService=mockAuthService()
// 1
让userInputs=Observable.CombineTest(登录名,密码){(登录名,密码)->(字符串,字符串)在
返回(登录名、密码)
}
// 2
authResponse=d按下按钮
.withLatestFrom(用户输入)
.flatMap{(登录名,密码)位于
返回mockAuthService.getAuthToken(使用login:login,mergedHash:password)
}
.map{authResponse在中
返回Auth(令牌:authResponse.token)
}
}
}
类MockAuthService{
func getAuthToken(带登录名:String,mergedHash:String)->可观察{
让dummyAuthResponse=AuthResponse(令牌:“dummyToken->login:\(登录),密码:\(合并)
返回可观察的。just(dummyAuthResponse)
}
}
ViewModel在其init方法中获取3个观察值,并将它们连接到其输出:

//1:将登录文本字段的最新值和密码文本字段的最新值合并为一个可观察值


//2:当用户按下按钮时,使用登录文本字段的最新值和密码文本字段的最新值,并使用
flatMap
将其传递给身份验证服务。当auth客户端返回一个
AuthResponse
时,将其映射到
auth
模型。将此“链”的结果设置为
ViewModel

authResponse
输出。这里的问题是您试图将“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) }

如果视图模型有多个输出,那么创建一个类(而不是从函数返回元组)可能是值得的,然后,RxSwift repo中的示例中的
GithubSignupViewModel1
是如何设置它的一个很好的示例。

第一种方法使用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
    })
  }

}
类ViewController:UIViewController{
@IBT弱var登录按钮:UIButton!
var-vm:ViewModel?
设disposebag=disposebag()
重写func viewDidLoad(){
super.viewDidLoad()
bindUi()
}
func bindUi(){
(loginBtn.rx.tap).bind(到:vm!.loginSbj).addDisposableTo(disposebag)
}
}
类视图模型{
让loginSbj=PublishSubject()
init(){
loginSbj.do(onNext:{uu}in
//做点什么
})
}
}
第二种方法是使用

类ViewController:UIViewController{
@IBT弱var登录按钮:UIButton!
var-vm:ViewModel?
重写func viewDidLoad(){
super.viewDidLoad()
bindUi()
}
func bindUi(){
loginBtn.rx.action=vm!.loginAction
}
}
类视图模型{
let loginAction:CoCoCoAction=CoCoCoAction{
//做点什么
}
}

非常感谢您!我真的很难弄清楚它是如何工作的,你的回答真的帮助了我。你应该尽量避免使用主题,在这种情况下你可以很容易地避免。@DanielT谢谢你的提醒!你完全正确,我在回答中更改了示例,使用RxSwift回购协议中建议的方式。看起来更好。我不知道你为什么把构造函数参数放在元组中。@BawenangRukmokoPardianPutra谢谢你的提问。关于您的问题:1)是的,如果您想使用依赖项注入,您不会在初始值设定项中这样做。2) 正确,在现实世界中,您必须添加错误处理。