1

I'm new to RxSwift and want to achieve the following. I have a email and password TextField. When you've entered a text in both textfields a button should be enabled.

In my ViewController I do the following:

txtEmail.rx.text.asObservable()
  .bindTo(viewModel.email)
  .addDisposableTo(disposeBag)

txtPassword.rx.text.asObservable()
  .bindTo(viewModel.password)
  .addDisposableTo(disposeBag)

viewModel.buttonEnabled
  .bindTo(btnLogin.rx.isEnabled)
  .addDisposableTo(disposeBag)

And here is my ViewModel:

import Foundation
import RxSwift
import RxCocoa

class LoginViewModel {

    let email = Variable<String?>("")
    let password = Variable<String?>("")

    var buttonEnabled: Observable<Bool>

    init() {

        var processedEmail: Observable<String>!
        var processedPassword: Observable<String>!

        processedEmail = email.asObservable().map(String.toLower as! (String?) -> String).map(String.trimWhiteSpace as! (String?) -> String)
        processedPassword = password.asObservable().map(String.toLower as! (String?) -> String).map(String.trimWhiteSpace as! (String?) -> String)

        let emailValid = processedEmail.asObservable().map(String.isNotEmpty)
        let passwordValid = processedPassword.asObservable().map(String.isNotEmpty)

        buttonEnabled = Observable.combineLatest(emailValid, passwordValid) {
            return $0 && $1
        }

    }

    func didTapLoginButton() {
        print("hello \(email.value)")
    }
}

For some reason the init method of my viewmodel never gets finished. Can someone help me?

Ketan Parmar
  • 27,092
  • 9
  • 50
  • 75
Steaphann
  • 2,797
  • 6
  • 50
  • 109

2 Answers2

3

It seems like the definition of processedEmail and processedPassword are generating the crash preventing the view model init to complete.

More specifically, the String.toLower and String.trimWhiteSpace method probably have type (String) -> String but here they are force cast to (String?) -> String.

processedEmail = email.asObservable()
  .map { $0 ?? "" }
  .map(String.toLower)
  .map(String.trimWhiteSpace)

processedPassword = password.asObservable()
  .map { $0 ?? "" }
  .map(String.toLower)
  .map(String.trimWhiteSpace)

I've only added .map { $0 ?? "" }. Because the original observable has type Observable<String?> the map call will transform it to Observable<String> which will then be processable by String.toLower and String.trimWhiteSpace

tomahh
  • 13,441
  • 3
  • 49
  • 70
1

I tried this example of init and it seems working:

init() {
    let processedEmail = email.asObservable()
        .map { $0 ?? "" }
        .map { $0.lowercased() }
        .map { !$0.isEmpty }

    let processedPassword = password.asObservable()
        .map { $0 ?? "" }
        .map { $0.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) }
        .map { !$0.isEmpty }

    buttonEnabled = Observable.combineLatest(processedEmail, processedPassword) {
        return $0 && $1
    }
}