web-dev-qa-db-ja.com

rx_tap(UIButton)を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
}
}
9
Marina

UIButtonのタップをObservableに変えて、UITextFieldsからの2つのObservableと一緒にViewModelに渡すことができます。

これは、シナリオの小さな実用的な例です。 (私は小さな認証クライアントのモッククラスを使用して、サービスからの応答をシミュレートしました):

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)
    }
}

これらは2つの興味深い部分です:

// 1:初期化するときに3つのObservableを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は、initメソッドで3つのObservableを取得し、それらを出力に接続します。

// 1:ログインテキストフィールドの最新値とパスワードテキストフィールドの最新値を1つのObservableに結合します。

// 2:ユーザーがボタンを押したら、ログインテキストフィールドの最新値とパスワードテキストフィールドの最新値を使用し、flatMapを使用して認証サービスに渡します。認証クライアントがAuthResponseを返したら、それをAuthモデルにマップします。この「チェーン」の結果をauthResponseViewModel出力として設定します

15
joern

最初のアプローチの使用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
    })
  }

}

2番目のアプローチは アクション を使用します

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
  }
}
6
user3165616

ここでの問題は、「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リポジトリの例から、それを設定する方法の優れた例です。

2
Daniel T.