CZ54
CZ54

Reputation: 5588

Rx Swift : Complex TextFields Validation

I'm new to RxSwift and all example I found are handling simple cases.

I'm trying to do form validation for my textfields. My custom TextField class has a method isValid() and a regexp. The isValid return is based on the regexp attribute.

So far, I have written the following :

let valids = [mLastName, mFirstName, mEmailField].map {
    $0.rx.text.map {
        text -> Bool in
        // I want more complex logic here
        // Like return field.isValid()
        return text!.characters.count > 0
    }
}    

let _ = Observable.combineLatest(valids) { iterator -> Bool in
    return iterator.reduce(true, { $0 && $1 })
}.subscribe(onNext: { allValid in
    ///update button according to AllValid
})

Does anyone know how to update the code to base the first Observable<Bool> be based on my isValid() method instead of text!.characters.count

Upvotes: 5

Views: 8109

Answers (2)

Michał Ciuba
Michał Ciuba

Reputation: 7944

There are probably many ways to do that.

You can use filter to transform rx.text Observable in your custom TextField class:

var isTextValid: Observable<Bool> {
    return rx.text.filter { _ in 
        return self.isValid()
    }
}

then you can combine isTextValid from all text fields with combineLatest.


You can also extract the validation logic from the custom text field (maybe you don't even need a custom text field at all).

Benefits:

  • validation could be easier to unit test
  • you can easily reuse validation in different places in your app (e.g. for UITextView if you ever use it).

The draft of a validator class:

class TextValidator {
    var input: Observable<String>
    var regex: NSRegularExpression

    init(input: Observable<String>, regex: NSRegularExpression) {
       self.input = input
       self.regex = regex
    }

    func validate() -> Observable<Bool> {
        return input.map { text in 
             //return true if regex matches the text
        }
    }       
}

Then you can use it as follows:

let mailValidator = TextValidator(input: mLastName.rx.text, regex: /* actual regex*/)
let firstNameValidator = TextValidator(input: mFirstName.rx.text, regex: ...)



let _ = Observable.combineLatest(mailValidator.validate(), firstName.validate(), ...) 
// and so on

Now if you want to write unit tests for the validators (which you probably should do), you can simply pass Observable.just("Some value") as input to TextValidator and verify what Observable returned by validate() does.

Upvotes: 6

CZ54
CZ54

Reputation: 5588

I found the answer myself. The problem was in the first map, I shouldn't use anonymous parameter.

See :

let valids = [mLastName, mFirstName, mEmailField].map { field in
    field.rx.text.map({ _ in return field.isValid() })
}

_ = Observable.combineLatest(valids) { iterator -> Bool in
    return iterator.reduce(true, { return $0 && $1 })
}.bindTo(self.mValidateButton.rx.isEnabled)

Upvotes: 4

Related Questions