Signals and Observer(Reactive Swift) для проверки формы не работает должным образом
Я делаю проверку формы, используя реактивный Swift. Но я столкнулся с проблемой сброса значения и значения сигнала.
Когда я заполняю все текстовое поле правильно, как указано правилом валидации, все сигналы (текстовое поле непрерывные текстовые значения) выдают истинное значение, которое позволяет мне отправлять данные формы. Я сбрасываю значения текстового поля после заполнения формы. После этого я отправляю ложное значение всему сигналу Observer. Но когда я начну заполнять текстовое поле, он получит предыдущий истинный сигнал и позволит мне отправлять данные без применения какого-либо правила проверки. это означает, что я не могу сбросить значение сигнала
Любая помощь могла бы быть полезна.
Моя проблема:
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 example@test.com".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
}
}
}
Я решил эту проблему, но не думаю, что это идеальный способ, а реактивный - не тот способ, которым я решил. Я жду идеального или самого принятого решения. Любая помощь или ответ действительно ценится.
2 ответа
Вот мой взгляд на это с более идиоматическим подходом (упрощенный только для двух входных данных для примера).
Во-первых, есть ViewModel, которая имеет MutableProperty
s для хранения входных значений. Вы можете инициализировать эти значения чем угодно nil
если вы хотите другие начальные значения для входов.
ViewModel также имеет свойства для проверки входных данных. Property.map
используется для вывода допустимых значений из входных данных. Кстати, вы можете использовать Signal.combineLatest(signal1, signal2, signal3, ...)
вместо signal1.combineLatest(with: signal2).combineLatest(with: signal3)...
Наконец, есть Action
который выполняет представление. В ViewController мы можем связать это Action
на кнопку. Действие отправляет пустую строку каждый раз, когда оно выполняется. .values
Сигнал действия используется для сброса входов после выполнения действия. Если представление может привести к ошибке, вы должны обработать это соответствующим образом.
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
}
}
Тогда есть настройка в 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)
}
}
Во-первых, MutableProperty
с ViewModel связаны с UITextField
s. Таким образом, текстовые поля не только инициализируются начальными значениями свойств в ViewModel, но также они обновляются, если обновляются свойства в ViewModel - таким образом, вы можете сбросить их при выполнении действия отправки.
Затем continuousTextValues
из UITextFields
привязаны к свойствам ViewModel. поскольку continuousTextValues
не срабатывает, если текст задан программно, только если он задан пользователем, это не создает цикл.
В заключение, CocoaAction
используется для связывания submit
действие на кнопку pressed
Действие. inputTransformer
Функция используется для отправки текущих значений входов при каждом нажатии кнопки.
Вы также можете подписаться на человека usernameValid
/ addressValid
свойства viewModel, чтобы установить ошибки проверки дисплея для пользователя здесь.
Ожидание ответа, который будет поддержан или для лучшего ответа.
Я пытался решить себя, как указано в вопросе.
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 example@test.com".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
import Foundation
import ReactiveSwift
class ContactViewModel {
var name = MutableProperty("")
var phoneNumber = MutableProperty("")
var emailAddress = MutableProperty("")
var location = MutableProperty("")
var comment = MutableProperty("")
}