pankaj
pankaj

Reputation: 8388

Using Combine to validate and submit form in UIKit

I am working on a form build with Storyboard(UIKit). I have 4 input fields and when it is valid I will submit the data to a UI and show response on UI. I have created a ViewModel and added the validation code there. Following is my validation code:

@Published public var firstName = ""
    @Published public var lastName = ""
    @Published public var phoneNumber = ""
    @Published public var emailAddress = ""    
var isFirstNameValidPublisher: AnyPublisher<Bool, Never> {
        $firstName
            .map { name in
                return name.count >= 3
            }
            .eraseToAnyPublisher()
    }
    
    var isLastNameValidPublisher: AnyPublisher<Bool, Never> {
        $lastName
            .map { name in
                return name.count >= 3
            }
            .eraseToAnyPublisher()
    }
    
    var isUserEmailValidPublisher: AnyPublisher<Bool, Never> {
          $emailAddress
              .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
              .map { phoneNumber in
                  return phoneNumber.count >= 8
              }
              .eraseToAnyPublisher()
      }
    
    public func isValidForm(firstName: String, lastName: String, phoneNum: String, emailAddress: String, isConsent: Bool) -> AnyPublisher<Bool, Never> {
        self.firstName = firstName
        self.lastName = lastName
        self.phoneNumber = phoneNum
        self.emailAddress = emailAddress
        
        return Publishers.CombineLatest4(
          isFirstNameValidPublisher,
          isLastNameValidPublisher,
          isUserEmailValidPublisher,
          isPhoneNumberValidPublisher)
            .map { isFirstNameValid, isLastNameValid, isPhoneNumberValid, isEmailValid in
                return isFirstNameValid && isLastNameValid && isPhoneNumberValid && isEmailValid && isConsent
            }
            .eraseToAnyPublisher()
    }

I Am trying to use this on submit button click in my ViewController as following:

cancellable = dependencies.leadConsumptionUseCase.isValidForm(firstName: firstNameTextView.text, lastName: lastNameTextView.text, phoneNum: phoneNumberTextView.text, emailAddress: emailTextView.text, isConsent: true).collect().sink(receiveCompletion: { completion in
        print("completed")
        print(completion)
    }, receiveValue: { res in
        print("result")
        print(res)
    })

I am able to validate the fields in ViewModel but somehow I am not getting any response in my ViewController. Also I am not sure how and where should I add the api call. I have already created another service class where I have an api call that will return the response as AnyCancellable. Most of the related examples are with SwiftUI but I am not using SwiftUI.

Edit: After removing collect from my isValidForm function call I am able to get the response but now it is coming multiple times. As a result of that my api call is executing multiple time.

Upvotes: 0

Views: 1331

Answers (1)

Scott Thompson
Scott Thompson

Reputation: 23701

There is a working example of how one might use this code below.

isValidForm() now just returns the publisher, it doesn't take in values. The values you are trying to validate are coming in through the four streams that you've constructed.

As I suggested, I've included .print operators so that you can see, in the debug output, how each stream participates in the validation.

In the main body of the playground I send values into the four streams one at a time. As each value is sent, the isValidForm() publisher emits a false value until all the fields have been sent. It then emits a true letting you know that all four fields have valid values.

import UIKit
import Combine

class Form : ObservableObject {
    @Published public var firstName = ""
    @Published public var lastName = ""
    @Published public var phoneNumber = ""
    @Published public var emailAddress = ""

    var isFirstNameValidPublisher: AnyPublisher<Bool, Never> {
            $firstName
                .map { name in
                    return name.count >= 3
                }
                .eraseToAnyPublisher()
        }
        
        var isLastNameValidPublisher: AnyPublisher<Bool, Never> {
            $lastName
                .map { name in
                    return name.count >= 3
                }
                .eraseToAnyPublisher()
        }
        
        var isUserEmailValidPublisher: AnyPublisher<Bool, Never> {
              $emailAddress
                  .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
                  .map { phoneNumber in
                      return phoneNumber.count >= 8
                  }
                  .eraseToAnyPublisher()
          }
        
        public func isValidForm() -> AnyPublisher<Bool, Never> {
            return Publishers.CombineLatest4(
                isFirstNameValidPublisher.print("first name"),
              isLastNameValidPublisher.print("last name"),
              isUserEmailValidPublisher.print("email"),
              isPhoneNumberValidPublisher.print("phone"))
                .map { isFirstNameValid, isLastNameValid, isPhoneNumberValid, isEmailValid in
                    return isFirstNameValid && isLastNameValid && isPhoneNumberValid && isEmailValid
                }
                .print("isValidForm")
                .eraseToAnyPublisher()
        }
}

let form = Form()
let validWatcher = form.isValidForm().sink { isValid in
    print("Is the form valid?: \(isValid)")
}
form.firstName = "Scott"
form.lastName = "Thompson"
form.phoneNumber = "(123) 555-1234}"
form.emailAddress = "[email protected]"

Upvotes: 2

Related Questions