Oksana
Oksana

Reputation: 119

RxSwift - Show/hide warning label when button was pressed

I'm using MVVM pattern and try to show warning labels only when user press the sign in button. Now they don't appear, because I don't know how to show them on user action only. Then warning for the corresponding label should hide while user starts to edit it. Here is my ViewController which handle reference to viewModel:

import UIKit
import RxSwift
import RxCocoa
class RxLoginViewController: UIViewController {

    @IBOutlet weak var signInButton: UIButton!
    @IBOutlet weak var phoneNumberTextField: UITextField!
    @IBOutlet weak var passwordTextField: UITextField!
    @IBOutlet weak var phoneWarningLabel: UILabel!
    @IBOutlet weak var passwordWarningLabel: UILabel!

    fileprivate var viewModel: RxLoginViewModel?
    private let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()

        viewModel = RxLoginViewModel(phoneNumber: phoneNumberTextField.rx.text.orEmpty.asDriver(), passwordText: passwordTextField.rx.text.orEmpty.asDriver())
        addBindsToViewModel(viewModel: viewModel!)
        setupButtons()
    }

    fileprivate func addBindsToViewModel(viewModel: RxLoginViewModel) {
        phoneNumberTextField.rx.text
            .orEmpty
            .asObservable()
            .debug("phoneNumberTextField")
            .bindTo(viewModel.phoneNumberText)
            .addDisposableTo(disposeBag)

        passwordTextField.rx.text
            .orEmpty
            .asObservable()
            .debug("passwordTextField")
            .bindTo(viewModel.passwordText)
            .addDisposableTo(disposeBag)

        viewModel.showPhoneWarning
            .asDriver()
            .debug("showPhoneWarning")
            .drive(onNext: { [weak self] showWarning in
                UIView.animate(withDuration: 0.2) {
                    self?.phoneWarningLabel.isHidden = !showWarning
                }}
            )
            .addDisposableTo(disposeBag)

        viewModel.showPasswordWarning
            .asDriver()
            .debug("showPasswordWarning")
            .drive(onNext: { [weak self] showWarning in
                UIView.animate(withDuration: 0.2) {
                    self?.passwordWarningLabel.isHidden = !showWarning
                }
            })
            .addDisposableTo(disposeBag)

        viewModel.credentialsValid
            .debug("credentialsValid")
            .drive(onNext: { [weak self] valid in
                self?.signInButton.isEnabled = valid
                self?.signInButton.alpha = valid ? 1 : 0.5
            })
            .addDisposableTo(disposeBag)
    }

    private func setupButtons() {

        signInButton.rx.tap
            .bindTo(viewModel!.signInAction)
            .addDisposableTo(disposeBag)
    }
}

And ViewModel:

class RxLoginViewModel {
    let dataManager = DataManager.sharedInstance()

    private let disposeBag = DisposeBag()

    //MARK: - Model proprties
    var phoneNumberText = Variable<String>("")
    var passwordText = Variable<String>("")
    var signInAction: Variable<Void> = Variable<Void>()

    var showPhoneWarning = Variable(false)
    var showPasswordWarning = Variable(false)
    var credentialsValid: Driver<Bool>
    var canLogIn = Variable(false)

    init(phoneNumber: Driver<String>, passwordText: Driver<String>) {

        let phoneValid = phoneNumber
            .distinctUntilChanged()
            .throttle(0.3)
            .map { ($0 =~ RegEx.phone) }

        let passwordValid = passwordText
            .distinctUntilChanged()
            .throttle(0.3)
            .map { ($0.utf8.count > 5) }

        credentialsValid = Driver.combineLatest(phoneValid, passwordValid) { $0 && $1 }
        phoneNumber.debug("phone number driver")
            .drive(onNext: {_ in self.showPhoneWarning.value = false })
            .addDisposableTo(disposeBag)
        phoneNumber.debug("password driver")
            .drive(onNext: {_ in self.showPasswordWarning.value = false })
            .addDisposableTo(disposeBag)

        credentialsValid.asObservable()
            .bindTo(canLogIn)
            .addDisposableTo(disposeBag)

        // actions handler
        signInAction
            .asObservable()
            .debug("signInAction")
            .do(onNext: { _ in
                 // show warning for any invalid textfield
                 // filter both valid fields
            } )
            // sent url request
            .subscribe(onNext: { status in

                    self.dataManager.login(withPhone: self.phoneNumberText.value, email: "", password: self.passwordText.value, success: { ( result ) in
                    if let response = result as? [String : Any], response["aboutMe"] == nil {
                        self.dataManager.currentUser().userId = response["id"] as? NSNumber
//                        self.dataManager.update(.fillProfile)
                    }
                }, failure: { (error) in
//                    print(error.localizedDescription)

                })
            })
            .addDisposableTo(disposeBag)
}

What should I do to show warning for any invalid textfield and how to filter both valid fields in my ViewModel?

Upvotes: 0

Views: 2749

Answers (1)

Daniel T.
Daniel T.

Reputation: 33967

I don't do my view models the same way you do, but hopefully this will help you out...

The key here is to outline everything that can affect the showPhoneWarning observable.

For example, you want the observable to emit true if signInAction comes in while phoneNumberText isn't valid.

Let's express that as a variable:

let invalidPhoneNumberOnTap = source.phoneNumberText
    .map { $0.isValidPhoneNumber == false }
    .sample(source.signInAction)

You also want the observable to emit false if phoneNumberText fires a new element.

let hideWarning = source.phoneNumberText.map { _ in false }

Now all you have to do is merge those two together. You also want to make sure it starts with false so the warning doesn't show prematurely.

showPhoneWarning = Observable.of(invalidPhoneNumberOnTap, hideWarning)
    .merge()
    .startWith(false)

Handle the showEmailWarning in much the same way.

Upvotes: 3

Related Questions