I'm very new to ReactiveSwift and MVVM as a whole. I'm trying to validate phone numbers entered into a textfield and enable/disable a button depending on the validation result.
In the app, there is a textfield and a UIButton button called Submit. For phone number validating, I'm using an open source library called [PhoneNumberKit][1]. It also provides a UITextField subclass which formats the user input.
I mashed together a solution that looks like this.
class ViewController: UIViewController {
@IBOutlet weak var textField: PhoneNumberTextField!
@IBOutlet weak var submitButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
textField.becomeFirstResponder()
submitButton.isEnabled = false
let textValuesSignal = textField.reactive.continuousTextValues
SignalProducer(textValuesSignal).start { result in
switch result {
case .value(let value):
print(value)
self.submitButton.isEnabled = self.isPhoneNumberValid(value)
case .failed(let error):
print(error)
self.submitButton.isEnabled = false
case .interrupted:
print("inturrupted")
self.submitButton.isEnabled = false
case .completed:
print("completed")
}
}
}
func isPhoneNumberValid(_ phoneNumberString: String) -> Bool {
do {
let phoneNumber = try PhoneNumberKit().parse(phoneNumberString)
let formattedPhoneNumber = PhoneNumberKit().format(phoneNumber, toType: .e164)
print("Phone number is valid: \(formattedPhoneNumber)")
return true
} catch let error {
print("Invalid phone number: \(error)")
return false
}
}
}
This does the job but not very elegantly. Also there is a significant lag between user input and the UI changing.
Another thing is my above solution doesn't conform to MVVM. I gave it another go.
class ViewController: UIViewController {
@IBOutlet weak var textField: PhoneNumberTextField!
@IBOutlet weak var submitButton: UIButton!
private let viewModel = ViewModel()
override func viewDidLoad() {
super.viewDidLoad()
textField.becomeFirstResponder()
submitButton.reactive.isEnabled <~ viewModel.isPhoneNumberValid
viewModel.phoneNumber <~ textField.reactive.continuousTextValues
}
}
class ViewModel {
let phoneNumber = MutableProperty("")
let isPhoneNumberValid = MutableProperty(false)
init() {
isPhoneNumberValid = phoneNumber.producer.map { self.validatePhoneNumber($0) } // Cannot assign value of type 'SignalProducer<Bool, NoError>' to type 'MutableProperty<Bool>'
}
private func validatePhoneNumber(_ phoneNumberString: String) -> Bool {
do {
let phoneNumber = try PhoneNumberKit().parse(phoneNumberString)
let formattedPhoneNumber = PhoneNumberKit().format(phoneNumber, toType: .e164)
print("Phone number is valid: \(formattedPhoneNumber)")
return true
} catch let error {
print("Invalid phone number: \(error)")
return false
}
}
}
I'm getting the below error in the initializer when I'm assigning the result from the validatePhoneNumber function's to the isPhoneNumberValid property.
Cannot assign value of type 'SignalProducer' to type 'MutableProperty'
I can't figure out how to hook up the phone number validation part with the submit button's isEnabled property and the tap action properly.
Try setting the property in
initand mapping the property itself rather than a producer:You’ll have to make
validatePhoneNumbera static method becauseselfwon’t be available yet.This is a more typical reactive way of doing this because you define one property completely in terms of another one during the view model’s initialization.