Search code examples
swiftformscheckboxrx-swiftrx-cocoa

validate form with checkbox button RxCocoa/RxSwift


I need to validate the form with a radio button, but I can't do it, I share my code:

My View:

"view"

private func registerForm() {
    tvUserName.rx.text.map { $0 ?? "" }.bind(to: viewModel.userNamePublishSubject).disposed(by: disposeBag)
    tvEmail.rx.text.map { $0 ?? "" }.bind(to: viewModel.emailPublishSubject).disposed(by: disposeBag)
    tvPassword.rx.text.map { $0 ?? "" }.bind(to: viewModel.passwordPublishSubject).disposed(by: disposeBag)
    tvRepeatPassword.rx.text.map { $0 ?? "" }.bind(to: viewModel.repeatPasswordPublishSubject).disposed(by: disposeBag)
    viewModel.formLoginNativaIsValid().bind(to: btnNext.rx.isEnabled).disposed(by: disposeBag)
    
    checkBoxOutlet.rx.tap
    .scan(false) { lastValue, _ in
        return !lastValue
    }
    .bind(to: btnNext.rx.isEnabled)
    .disposed(by: disposeBag)
}

My ViewModel:

"viewmodel"

let userNamePublishSubject = PublishSubject<String>()
let emailPublishSubject = PublishSubject<String>()
let passwordPublishSubject = PublishSubject<String>()
let repeatPasswordPublishSubject = PublishSubject<String>()

func formLoginNativaIsValid() -> Observable<Bool> {
    return Observable.combineLatest(userNamePublishSubject.asObserver(),
        emailPublishSubject.asObserver(),
        passwordPublishSubject.asObserver(),
        repeatPasswordPublishSubject.asObserver()).map { userName, email, password, repeatPassword in
            return self.validateRegister(userName: userName, email: email, password: password,
                                         repeatPassword: repeatPassword)
    }.startWith(false)
}

private func validateRegister(userName: String, email: String, password: String, repeatPassword: String) -> Bool {
    return userName.count >= 5 && email.isEmail() && password.count >= 5 && repeatPassword.count >= 5 && password == repeatPassword
}

I have validated the form, but I also need the user to accept the terms and conditions for the "next" button to be enabled.


Solution

  • When designing a reactive system, you need to think about cause and effect. Each observable chain should focus on a single effect and bring in as many causes as needed for that effect. Just like a function has several parameters passed in and returns a single value.

    In this case, your effect is btnNext.rx.isEnabled. And you have to consider what causes that effect to change? Here is an example of how I would write it:

    class View: UIView {
        weak var tvUserName: UITextView!
        weak var tvEmail: UITextView!
        weak var tvPassword: UITextView!
        weak var tvRepeatPassword: UITextView!
        weak var btnNext: UIButton!
        weak var checkBoxOutlet: UIButton!
    
        let disposeBag = DisposeBag()
    
        override func awakeFromNib() {
            super.awakeFromNib()
    
            isNextButtonEnabled(
                username: tvUserName.rx.text.orEmpty.asObservable(),
                email: tvEmail.rx.text.orEmpty.asObservable(),
                password: tvPassword.rx.text.orEmpty.asObservable(),
                repeatPassword: tvRepeatPassword.rx.text.orEmpty.asObservable(),
                checkBox: checkBoxOutlet.rx.tap.asObservable()
            )
            .bind(to: btnNext.rx.isEnabled)
            .disposed(by: disposeBag)
        }
    }
    
    func isNextButtonEnabled(username: Observable<String>, email: Observable<String>, password: Observable<String>, repeatPassword: Observable<String>, checkBox: Observable<Void>) -> Observable<Bool> {
        return Observable.combineLatest(
            [
                username.map { $0.count >= 5 },
                email.map { $0.isEmail() },
                password.map { $0.count >= 5 },
                Observable.combineLatest(password, repeatPassword) { $0 == $1 },
                checkBox.scan(false) { state, _ in !state }.startWith(false)
            ]
        )
        .map { $0.allSatisfy { $0 } }
    }
    

    Notice how simple your view model is here. It's the isNextButtonEnabled function. It can be tested very easily but is so simple that you might not even feel the need to test. All the conditions that determine if the next button is enabled are gathered up in a single place for easy understanding. No need for a bunch of PublishSubjects. It's a lot easier to read and understand.