Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using RxSwift, How To Enable UIButton Based on Valid Text?

In RxSwift/RxCocoa 2.0.0- beta 3, I have a ViewModel with:

let someString = Variable("")

func isValidSomeString() -> Observable<Bool>  {

    if someString.value.characters.count == 0 {
        return just(false)
    }
    return just(true)
}   

I have the someString bound already to a text field in the ViewController.

Whenever the someString (or perhaps the text field if that's a better way) changes, I want a button enabled based on if someString is valid.

I tried using the "Observable< Bool >", but started going down another path. I could do this in the ViewController:

    someViewModel.someString.subscribeNext { text -> Void in

        // could just someUIButton.enabled = someViewModel.isValidSomeString(text)

    }.addDisposableTo(disposeBag)

Isn't there another way that is less verbose than the isValidSomeString(text) approach? We already have had nice success with a isValidLogin that returns Observable< Bool > which used combineLatest.

like image 891
finneycanhelp Avatar asked Nov 30 '15 13:11

finneycanhelp


2 Answers

Sounds like Action is the perfect tool to use in this case. You'll want to create an Action in your ViewModel, let its enabledIf observable be the result of changes to the string, and connect this action to your UIButton. This way the button will be auto-enabled.

In your ViewModel, you'll need to add this:

var buttonAction: CocoaAction {
    let hasString = self.someString.map { !$0.isEmpty }
    return CocoaAction(enabledIf: hasString) {
        // do something when button tapped
        return empty()
    }
}

And when binding your ViewController to your ViewModel, you'll do:

myButton.rx_action = viewModel.buttonAction
like image 53
fpillet Avatar answered Oct 22 '22 18:10

fpillet


Looks like you just need to map your someString to Obserable<Bool> and then bind it to rx_enabled of UIButton Something like this:

someViewModel.someString
             .asObservable()
             .map { (str) -> Bool in
                 return someViewModel.isValidSomeString(text)
             }
            .bindTo(someUIButton.rx_enabled)
            .addDisposableTo(disposeBag)  

but I think it whould be simpler if you would have Variable<Bool> in your viewmodel kind of

struct ViewModel {
....
    canLogIn = Variable(false)
}

In this case you will be able bind it to rx_enabled like this:

  let userNameValidation = loginTextField
     .rx_text
     .map({!$0.isEmpty})
     .shareReplay(1)

  let passwordValidation = passwordTextField
     .rx_text
     .map({!$0.isEmpty})
     .shareReplay(1)

  let loginEnabled = combineLatest(userNameValidation, passWordValidation) { (username, password) in
     return username && password
  }

  loginEnabled
     .bindTo(loginViewModel.canlogIn)
     .addDisposableTo(disposeBag)

  loginViewModel.canlogIn
         .bindTo(loginButton.rx_enabled)
         .addDisposableTo(disposeBag)
like image 41
Serg Dort Avatar answered Oct 22 '22 18:10

Serg Dort