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?
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()