1

I am new to Combine and trying to use it in my application for validation of a form. It is a typical form with first name, last name, email and phoneNumber. There will be a submit button which will be initially disabled and will become active once the validations are passed.

Following is my UIViewController code:

firstNameTextView.text = dependencies.leadConsumptionUseCase.firstName
    lastNameTextView.text = dependencies.leadConsumptionUseCase.lastName
    phoneNumberTextView.text = dependencies.leadConsumptionUseCase.phoneNumber
    emailTextView.text = dependencies.leadConsumptionUseCase.emailAddress

    self.cancellable = dependencies.leadConsumptionUseCase.isSignupFormValidPublisher.receive(on: RunLoop.main).sink(receiveValue: { isValid in
        self.continueButton.isEnabled = isValid
    })

Viewmodel Protocol:

    public protocol LeadConsumptionProtocol {
    func setLead(_ lead: Lead)

    func saveLead(leadConsumptionDetails: Lead) -> AnyPublisher<Void, Error>
    var isSignupFormValidPublisher: AnyPublisher<Bool, Never> { get }
    
    var firstName: String { get set}
    var lastName: String { get set}
    var phoneNumber: String { get set}
    var emailAddress: String { get set}
}

ViewModel/Interactor code:

@Published public var firstName = ""
    @Published public var lastName = ""
    @Published public var phoneNumber = ""
    @Published public var emailAddress = ""
var isFirstNameValidPublisher: AnyPublisher<Bool, Never> {
        $firstName
            .removeDuplicates()
            .map { name in
                return name.count >= 3
            }
            .eraseToAnyPublisher()
    }
    
    var isLastNameValidPublisher: AnyPublisher<Bool, Never> {
        $lastName
            .removeDuplicates()
            .map { name in
                return name.count >= 3
            }
            .eraseToAnyPublisher()
    }
    
    var isUserEmailValidPublisher: AnyPublisher<Bool, Never> {
          $emailAddress
            .removeDuplicates()
              .map { email in
                  let emailPredicate = NSPredicate(format:"SELF MATCHES %@", "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}")
                  return emailPredicate.evaluate(with: email)
              }
              .eraseToAnyPublisher()
      }
      
      var isPhoneNumberValidPublisher: AnyPublisher<Bool, Never> {
          $phoneNumber
              .removeDuplicates()
              .map { phoneNumber in
                  return phoneNumber.count >= 8
              }
              .eraseToAnyPublisher()
      }
    
    public var isSignupFormValidPublisher: AnyPublisher<Bool, Never> {
        Publishers.CombineLatest4(
            isFirstNameValidPublisher,
            isLastNameValidPublisher,
            isPhoneNumberValidPublisher,
            isUserEmailValidPublisher)
          .map { isNameValid, isEmailValid, isPasswordValid, passwordMatches in
              return isNameValid && isEmailValid && isPasswordValid && passwordMatches
          }
          .eraseToAnyPublisher()
      }

I assume that binding is not working properly. What can I try next?

pankaj
  • 7,878
  • 16
  • 69
  • 115
  • What does `I assume that binding is not working properly` mean? What error do you get? What is working what is not? Also, isn´t this the same question: -> [question](https://stackoverflow.com/questions/73525920/using-combine-to-validate-and-submit-form-in-uikit) ? – burnsi Aug 29 '22 at 23:27
  • @burnsi I meant when user makes any changes in the textfield it should reflect in the corresponding Published variable in ViewModel – pankaj Aug 30 '22 at 06:37
  • @ScottThompson In the answer above the values are validated at the button click but now I want to perform the validation as user enters values in the form. I have seen some examples of it in SwiftUI but not in UIKit. I believe my main problem is about setting up of bindings, rest should work – pankaj Aug 30 '22 at 06:39
  • The titles of these two similar questions seem to indicate to the general reader that they are, in fact, duplicates. Could you edit this one (the second of the pair) to amend the title, so that it reflects the difference between the two? – halfer Sep 03 '22 at 21:42

1 Answers1

0

Your ValidPublishers are computed properties, meaning they'll create new Publishers every time you access them.

When you mark a property using @Published, you can access that property's publisher using the $ sign (i.e: $firstName) then in the map you return your logic:

public var isSignupFormValidPublisher: AnyPublisher<Bool, Never>

And in the viewModel init:

isSignupFormValidPublisher = Publishers.Zip4(//Zip to listen to any Publisher output, unlike CombineLatest which requires all of them to change
        $firstName
        $lastName,
        $phoneNumber,
        $emailAddress)
    .map { firstName, lastName, phoneNumber, email in
        return yourLogic
    }.eraseToAnyPublisher()
Timmy
  • 4,098
  • 2
  • 14
  • 34