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, которая имеет MutablePropertys для хранения входных значений. Вы можете инициализировать эти значения чем угодно 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 связаны с UITextFields. Таким образом, текстовые поля не только инициализируются начальными значениями свойств в 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("")

}
Другие вопросы по тегам