【问题标题】:How to bind rx_tap (UIButton) to ViewModel?如何将 rx_tap (UIButton) 绑定到 ViewModel?
【发布时间】:2016-11-17 06:50:18
【问题描述】:

我有具有 2 个 UITextField 属性和 1 个 UIButton 的授权控制器。我想将我的视图绑定到 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
}
}

【问题讨论】:

    标签: ios swift rx-swift


    【解决方案1】:

    您可以将 UIButton 上的点击转换为 Observable,并将其与 UITextFields 中的两个 Observable 一起交给 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)
        }
    }
    

    这是两个有趣的部分:

    // 1:我们在初始化ViewModel时将三个Observables注入到它中。

    // 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 在其 init 方法中获取 3 个 Observable 并将它们连接到其输出:

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

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

    【讨论】:

    • 非常感谢!我很难弄清楚它是如何工作的,你的回答对我很有帮助。
    • 你应该尽可能避免使用主题,在这种情况下你可以很容易地避免。
    • @DanielT 感谢您的评论!您完全正确,我将答案中的示例更改为使用 RxSwift 存储库中建议的方式。
    • 看起来更好。我不确定你为什么将构造函数参数放在一个元组中。
    • @BawenangRukmokoPardianPutra 感谢您的提问。关于您的问题:1)是的,如果您想使用依赖注入,您不会在初始化程序中这样做。 2)正确,在现实世界中你必须添加错误处理。
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      这里的问题是你试图让你的“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) }
      

      通过在您的 viewDidLoad 中执行此操作来设置它:

      let auth = viewModel(loginTxtField.rx_text, passwordTxtField.rx_text, button.rx_tap)
      

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

      【讨论】:

        猜你喜欢
        • 2020-03-06
        • 2014-08-06
        • 2011-11-30
        • 2015-12-31
        • 1970-01-01
        • 1970-01-01
        • 2015-10-30
        • 2011-08-04
        • 1970-01-01
        相关资源
        最近更新 更多