Reputation: 31313
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.
Upvotes: 2
Views: 823
Reputation: 5941
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.
Upvotes: 3