Sandeep Bhandari
Sandeep Bhandari

Reputation: 20379

How to create `RxAction` to enable/disable button if text in `UITextField` is valid?

I have multiple textFields the criteria for their validation is very straight forward.

For sake of simplifying problem statement, lets assume the criteria are

First name should have at least one character,
Last name should have at least one character
and Age should be greater than 0

Assume I have a validator action as

   validatorAction = Action<Void,Bool> {
        return Single<Bool>.create(subscribe: { (single) -> Disposable in
            if self.firstName.text?.count ?? 0 > 0 && self.lastName.text?.count ?? 0 > 0 && self.age.text?.count ?? 0 > 0 {
                single(.success(true))
            }
            else {
                single(.success(false))
            }
            return Disposables.create()
        })
   }

I want this action to be executed every time character in any of the textfield changes, so I bind this action to UIControlEvents.editingChanged

    self.firstName.rx.bind(to: validatorAction, controlEvent: self.firstName.rx.controlEvent(UIControlEvents.editingChanged)){ (textField) -> Void in
        return Void()
    }

    self.lastName.rx.bind(to: validatorAction, controlEvent: self.lastName.rx.controlEvent(UIControlEvents.editingChanged)){ (textField) -> Void in
        return Void()
    }

    self.age.rx.bind(to: validatorAction, controlEvent: self.age.rx.controlEvent(UIControlEvents.editingChanged)) { (textField) -> Void in
        return Void()
    }

Finally, I want the action to control the enable and disable state of my button

        validatorAction.elements
        .asObservable()
        .startWith(false)
        .asDriver(onErrorJustReturn: false)
        .drive(self.submitButton.rx.isEnabled)
        .disposed(by: self.disposeBag)

Unfortunately though code works absolutely fine, every time the validatorAction is executed, textField looses its focus. That causes a huge user experience problem.

I raised a issue to RxAction repo and got the response

Hi there! It sounds like you're slightly misusing the closure that's passed into the Action initializer. You'll want to try using the full initializer Action(enabledIf:, workFactory:) to separate out one closure to enable/disable the action, and another closure to return an observable of whatever work you want done. You can then bind the action to the button and it will handle enabling/disabling it for you. Check out the readme for more information and let us know how it goes.

But did not get much out of it about how to use it. Can somebody show concrete code with how to use Actions to achieve the intended behavior.

Upvotes: 0

Views: 1258

Answers (1)

Farooq Zaman
Farooq Zaman

Reputation: 505

In my opinion a better approach would be to do bind Variables with textfields and do validation on Variables.

var firstName = Variable<String>("")
var lastName = Variable<String>("")
var age = Variable<String>("")

tfFirstName.rx.text.orEmpty.bind(to: firstName).disposed(by: rx_disposeBag)
tfLastName.rx.text.orEmpty.bind(to: lastName).disposed(by: rx_disposeBag)
tfAge.rx.text.orEmpty.bind(to: age).disposed(by: rx_disposeBag)

Then you can do validation like this:

var isValid : Observable<Bool>{
    return Observable.combineLatest( firstName.asObservable(), lastName.asObservable(), age.asObservable()){ (fname, lname, userAge) in
        return fname.count > 0 && lname.count > 0 && userAge.count > 0
    }
}

Finally, you can bind Action submitButton like this:

submitButton.rx.action = Action(enabledIf: isValid, workFactory:{input in
    //Action code here
})

I hope that answers your question.

Upvotes: 4

Related Questions