Reputation: 121
I am doing form validation using reactive swift. But I faced issue on resetting value & signal value.
When I fill all the textfield correctly as directed by validation rule all signals(textfield continuoustextvalues) produce true value, which will allow me to send form data. I reset values of textfield after completion of form submission. After that I send false value to all signal Observer. But when I start filling textfield it will get previous true signal and allow me to send data without any validation rule applied. that means I can't reset signal value
Any help would be really appreciated.
My Problem:
import UIKit
import ReactiveSwift
import Result
class ContactVC: BaseViewController {
@IBOutlet weak var textFieldName: JVFloatLabeledTextField!
@IBOutlet weak var textFieldPhoneOL: JVFloatLabeledTextField!
@IBOutlet weak var textViewComent: UITextView!
@IBOutlet weak var textFieldLocationOL: JVFloatLabeledTextField!
@IBOutlet weak var textFieldEmailOL: JVFloatLabeledTextField!
@IBOutlet weak var btnSubmitOL: PGSpringAnimation!
var (nameValidationSignal, nameValidationObserver) = Signal<Bool, NoError>.pipe()
var (phoneValidationSignal, phoneValidationObserver) = Signal<Bool, NoError>.pipe()
var (emailValidationSignal, emailValidationObserver) = Signal<Bool, NoError>.pipe()
var (locationValidationSignal, locationValidationObserver) = Signal<Bool, NoError>.pipe()
var (commentValidationSignal, commentValidationObserver) = Signal<Bool, NoError>.pipe()
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.formValidation()
}
// MARK: - submit button action
@IBAction func btnSubmitAction(_ sender: Any) {
let params = ["name":textFieldName.text!,"email":textFieldEmailOL.text!,"location":textFieldLocationOL.text!,"message":textViewComent.text!,"phone":textFieldPhoneOL.text!]
APIManager(urlString:enumUrl.ContactAdmin.mainURL(),parameters:params as [String : AnyObject]?,method: .post).handleResponse(viewController: self, progressMessage: "downloading", completionHandler: { (response : AllResponse) in
self.nameValidationObserver.send(value: false)
self.emailValidationObserver.send(value: false)
self.phoneValidationObserver.send(value: false)
self.locationValidationObserver.send(value: false)
self.commentValidationObserver.send(value: false)
self.btnSubmitOL.backgroundColor = UIColor.gray
self.btnSubmitOL.isUserInteractionEnabled = false
})
}
// MARK: - validation textfield
func formValidation(){
self.btnSubmitOL.backgroundColor = UIColor.gray
self.btnSubmitOL.isUserInteractionEnabled = false
// Create signals
// Signals for TextFields
self.nameValidationSignal = self.textFieldName.reactive.continuousTextValues
.map{ ($0?.characters.count ?? 0) >= 3 }
self.phoneValidationSignal = self.textFieldPhoneOL.reactive.continuousTextValues
.map{ ($0?.characters.count ?? 0 ) >= 8 }
self.emailValidationSignal = self.textFieldEmailOL.reactive.continuousTextValues
.map{ $0?.isEmail ?? false }
self.locationValidationSignal = self.textFieldLocationOL.reactive.continuousTextValues
.map{ ($0?.characters.count ?? 0) >= 3 }
self.commentValidationSignal = self.textViewComent.reactive.continuousTextValues
.map{ ($0?.characters.count ?? 0) >= 5 }
// Observe TextFields Singals for Changing UI
self.nameValidationSignal.observeValues { value in
self.textFieldName.floatingLabelActiveTextColor = value ? UIColor.red : UIColor.black
self.textFieldName.floatingLabel.text = value ? "name".localize : "Name must be greater than 4 characters".localize
}
self.phoneValidationSignal.observeValues { value in
self.textFieldPhoneOL.floatingLabelActiveTextColor = value ? UIColor.red : UIColor.black
self.textFieldPhoneOL.floatingLabel.text = value ? "phone".localize : "Phone must be greater than 7 characters".localize
}
self.emailValidationSignal.observeValues { value in
self.textFieldEmailOL.floatingLabelActiveTextColor = value ? UIColor.red : UIColor.black
self.textFieldEmailOL.floatingLabel.text = value ? "email".localize : "Email must be of type [email protected]".localize
}
self.locationValidationSignal.observeValues { value in
self.textFieldLocationOL.floatingLabelActiveTextColor = value ? UIColor.red : UIColor.black
self.textFieldLocationOL.floatingLabel.text = value ? "location".localize : "Loation must be greater than 4 characters".localize
}
self.commentValidationSignal.observeValues { value in
self.textViewComent.textColor = value ? UIColor.red : UIColor.black
}
let formValidationSignal = nameValidationSignal.combineLatest(with: phoneValidationSignal).combineLatest(with: emailValidationSignal).combineLatest(with: locationValidationSignal).combineLatest(with: commentValidationSignal)
.map {
$0.0.0.0 && $0.0.0.1 && $0.0.1 && $0.1 && $1
}
formValidationSignal.observeValues {
self.btnSubmitOL.isUserInteractionEnabled = $0
self.btnSubmitOL.backgroundColor = $0 ? UIColor.appRedColor() : UIColor.gray
}
}
}
I have made solution to this problem but I don't think it's perfect way and the reactive is not way I have done to solve. I am waiting for perfect or most accepted Solution. Any help or answer is really Appreciated.
Upvotes: 2
Views: 3275
Reputation: 3357
Here is my take on this with a more idiomatic approach (simplified to only two inputs for the sake of the example).
First, there is a ViewModel that has MutableProperty
s to hold the input values. You could initialize these values to anything else than nil
if you want other initial values for the inputs.
The ViewModel als has a properties for the validation of the inputs.
Property.map
is used to infer valid values from the input. Btw, you can use Signal.combineLatest(signal1, signal2, signal3, ...)
instead of signal1.combineLatest(with: signal2).combineLatest(with: signal3)...
Finally, there's an Action
that performs the submission. In the ViewController, we can bind this Action
to the button. The Action sends an empty string each time it is performed. The .values
signal of the action is used to reset the inputs after the action is performed.
If the submission could produce an error, you should handle this accordingly.
class ViewModel {
let username = MutableProperty<String?>(nil)
let address = MutableProperty<String?>(nil)
let usernameValid: Property<Bool>
let addressValid: Property<Bool>
let valid: Property<Bool>
let submit: Action<(String?, String?), String, NoError>
init() {
self.usernameValid = username.map {
return ($0 ?? "").characters.count > 0
}
self.addressValid = address.map {
return ($0 ?? "").characters.count > 0
}
self.valid = Property.combineLatest(self.usernameValid, self.addressValid).map { (usernameValid, addressValid) in
return usernameValid && addressValid
}
self.submit = Action(enabledIf: self.valid) { input in
print("Submit with username \(input.0) and address \(input.1)")
return SignalProducer<String, NoError>(value: "")
}
self.username <~ self.submit.values
self.address <~ self.submit.values
}
}
Then there's the setup in the ViewController:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.username.reactive.text <~ self.viewModel.username
self.address.reactive.text <~ self.viewModel.address
self.viewModel.username <~ self.username.reactive.continuousTextValues
self.viewModel.address <~ self.address.reactive.continuousTextValues
self.submit.reactive.pressed = CocoaAction(self.viewModel.submit) { [weak self] (button) -> (String?, String?) in
return (self?.username.text, self?.address.text)
}
}
First, the MutableProperty
s of the ViewModel are bound to the UITextField
s. This way, the text fields are not only initialised to the initial values of the properties in the ViewModel, but also they are updated if the properties in the ViewModel are updated - this way you can reset them when the submit action is performed.
Then, the continuousTextValues
of the UITextFields
are bound to the properties of the ViewModel. Since continuousTextValues
does not fire if the text is set programatically, only if it is set by the User, this does not create a loop.
Finally, CocoaAction
is used to bind the submit
action to the button's pressed
Action. The inputTransformer
function is used to send the current values of the inputs each time the button is pressed.
You can also subscribe to the individual usernameValid
/ addressValid
properties of the viewModel to set display validation errors to the user here.
Upvotes: 10
Reputation: 121
Waiting for answer to be supported or for better answer.
I tried to solve myself as stated in question.
import UIKit
import ReactiveSwift
import Result
class ContactVC: BaseViewController {
@IBOutlet weak var textFieldName: JVFloatLabeledTextField!
@IBOutlet weak var textFieldPhoneOL: JVFloatLabeledTextField!
@IBOutlet weak var textViewComent: UITextView!
@IBOutlet weak var textFieldLocationOL: JVFloatLabeledTextField!
@IBOutlet weak var textFieldEmailOL: JVFloatLabeledTextField!
@IBOutlet weak var btnSubmitOL: PGSpringAnimation!
// Singals Start
var nameSignal:SignalProducer<Bool, NoError>!
var phoneSignal:SignalProducer<Bool, NoError>!
var emailSignal:SignalProducer<Bool, NoError>!
var locationSignal:SignalProducer<Bool, NoError>!
var commentSignal:SignalProducer<Bool, NoError>!
// Signals End
private var viewModel = ComtactViewModel()
override func viewDidLoad() {
super.viewDidLoad()
checkLocationAuthorizationStatus()
setupBindings()
}
func setupBindings() {
//binding to view model to UI
self.textFieldName.reactive.text <~ self.viewModel.name
self.textFieldPhoneOL.reactive.text <~ self.viewModel.phoneNumber
self.textFieldEmailOL.reactive.text <~ self.viewModel.emailAddress
self.textFieldLocationOL.reactive.text <~ self.viewModel.location
self.textViewComent.reactive.text <~ self.viewModel.comment
}
// MARK: - submit button action
@IBAction func btnSubmitAction(_ sender: Any) {
self.btnSubmitOL.isUserInteractionEnabled = false
let params = ["name":textFieldName.text!,"email":textFieldEmailOL.text!,"location":textFieldLocationOL.text!,"message":textViewComent.text!,"phone":textFieldPhoneOL.text!]
APIManager(urlString:enumUrl.ContactAdmin.mainURL(),parameters:params as [String : AnyObject]?,method: .post).handleResponse(viewController: self, progressMessage: "downloading", completionHandler: { (response : AllResponse) in
self.viewModel.name.value = ""
self.viewModel.phoneNumber.value = ""
self.viewModel.emailAddress.value = ""
self.viewModel.location.value = ""
self.viewModel.comment.value = ""
Utilities.showAlert(alertTitle: "sucess", alertMessage: response.message!, viewController: self, didTabOkButton: {
self.btnSubmitOL.backgroundColor = UIColor.gray
self.btnSubmitOL.isUserInteractionEnabled = false
}, didTabOnCancelButton: nil)
})
}
// MARK: - validation textfield
func formValidation(){
self.btnSubmitOL.backgroundColor = UIColor.gray
self.btnSubmitOL.isUserInteractionEnabled = false
// Create signals
// Signals for ViewModels for crossCheck
self.nameSignal = self.viewModel.name.producer.map{ $0.characters.count >= 3 }.producer
self.phoneSignal = self.viewModel.phoneNumber.producer.map{ $0.characters.count >= 8 }.producer
self.emailSignal = self.viewModel.emailAddress.producer.map{ $0.isEmail }.producer
self.locationSignal = self.viewModel.location.producer.map{ $0.characters.count >= 3 }.producer
self.commentSignal = self.viewModel.comment.producer.map{ $0.characters.count >= 5 }.producer
// Signals for TextFields
self.textFieldName.reactive.continuousTextValues.skipNil()
.observeValues { self.viewModel.name.value = $0 }
self.textFieldPhoneOL.reactive.continuousTextValues.skipNil()
.observeValues { self.viewModel.phoneNumber.value = $0 }
self.textFieldEmailOL.reactive.continuousTextValues.skipNil()
.observeValues { self.viewModel.emailAddress.value = $0 }
self.textFieldLocationOL.reactive.continuousTextValues.skipNil()
.observeValues{ self.viewModel.location.value = $0 }
self.textViewComent.reactive.continuousTextValues.skipNil()
.observeValues { self.viewModel.comment.value = $0 }
// Observe TextFields Singals for Changing UI
self.nameSignal.startWithValues { value in
self.textFieldName.textColor = value ? UIColor.appRedColor() : UIColor.black
self.textFieldName.floatingLabel.text = value ? "name".localize : "Name must be greater than 4 characters".localize
}
self.phoneSignal.startWithValues { value in
self.textFieldPhoneOL.textColor = value ? UIColor.appRedColor() : UIColor.black
self.textFieldPhoneOL.floatingLabel.text = value ? "phone".localize : "Phone must be greater than 7 characters".localize
}
self.emailSignal.startWithValues { value in
self.textFieldEmailOL.textColor = value ? UIColor.appRedColor() : UIColor.black
self.textFieldEmailOL.floatingLabel.text = value ? "email".localize : "Email must be of type [email protected]".localize
}
self.locationSignal.startWithValues { value in
self.textFieldLocationOL.textColor = value ? UIColor.appRedColor() : UIColor.black
self.textFieldLocationOL.floatingLabel.text = value ? "location".localize : "Loation must be greater than 4 characters".localize
}
self.commentSignal.startWithValues { value in
self.textViewComent.textColor = value ? UIColor.appRedColor() : UIColor.black
}
let formValidationViewModelSignal = self.nameSignal.combineLatest(with: self.phoneSignal).combineLatest(with: self.emailSignal).combineLatest(with: self.locationSignal).combineLatest(with: self.commentSignal).map {
$0.0.0.0 && $0.0.0.1 && $0.0.1 && $0.1 && $1
}
formValidationViewModelSignal.startWithValues {
self.btnSubmitOL.isUserInteractionEnabled = $0
self.btnSubmitOL.backgroundColor = $0 ? UIColor.appRedColor() : UIColor.gray
}
}
ContactView Model Class
import Foundation
import ReactiveSwift
class ContactViewModel {
var name = MutableProperty("")
var phoneNumber = MutableProperty("")
var emailAddress = MutableProperty("")
var location = MutableProperty("")
var comment = MutableProperty("")
}
Upvotes: 0