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 init
and mapping the property itself rather than a producer:
let isPhoneNumberValid: Property<Bool>
init() {
isPhoneNumberValid = phoneNumber.map { ViewModel.validatePhoneNumber($0) }
}
You’ll have to make validatePhoneNumber
a static method because self
won’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.