Steaphann
Steaphann

Reputation: 2777

enable button with RxSwift logic

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?

Upvotes: 1

Views: 2184

Answers (2)

Anton Efimenko
Anton Efimenko

Reputation: 51

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

Upvotes: 1

tomahh
tomahh

Reputation: 13651

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

Upvotes: 3

Related Questions