【问题标题】:Signals and Observer(Reactive Swift) for form validation not working as expected用于表单验证的信号和观察者(反应式 Swift)未按预期工作
【发布时间】:2017-06-01 02:52:40
【问题描述】:

我正在使用响应式 swift 进行表单验证。但是我在重置值和信号值时遇到了问题。

当我按照验证规则的指示正确填写所有文本字段时,所有信号(文本字段连续文本值)都会产生真值,这将允许我发送表单数据。完成表单提交后,我重置了文本字段的值。之后,我向所有信号观察者发送错误值。但是当我开始填充文本字段时,它将获得先前的真实信号,并允许我在不应用任何验证规则的情况下发送数据。这意味着我无法重置信号值

任何帮助将不胜感激。

我的问题:

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
        }
    }

}

我已经解决了这个问题,但我认为这不是完美的方法,而且反应式不是我解决的方法。 我正在等待完美或最被接受的解决方案。 非常感谢任何帮助或答案。

【问题讨论】:

    标签: swift mvvm functional-programming reactive-cocoa bidirectional


    【解决方案1】:

    等待答案得到支持或更好的答案。

    我试图解决问题。

    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("")
    
    }
    

    【讨论】:

      【解决方案2】:

      这是我用更惯用的方法对此的看法(为了示例,简化为只有两个输入)。

      首先,有一个 ViewModel 具有 MutablePropertys 来保存输入值。如果您想要输入的其他初始值,您可以将这些值初始化为 nil 以外的任何值。

      ViewModel 还具有用于验证输入的属性。 Property.map 用于从输入推断有效值。顺便说一句,您可以使用Signal.combineLatest(signal1, signal2, signal3, ...) 而不是signal1.combineLatest(with: signal2).combineLatest(with: signal3)...

      最后,有一个Action 执行提交。在 ViewController 中,我们可以将这个 Action 绑定到按钮上。 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)
          }
      }
      

      首先,ViewModel 的MutablePropertys 绑定到UITextFields。这样,文本字段不仅被初始化为 ViewModel 中属性的初始值,而且如果 ViewModel 中的属性被更新,它们也会被更新 - 这样您可以在执行提交操作时重置它们。

      然后,UITextFields 的continuousTextValues 绑定到 ViewModel 的属性。由于continuousTextValues 不会在以编程方式设置文本时触发,只有当它由用户设置时,这不会创建循环。

      最后,CocoaAction 用于将submit 操作绑定到按钮的pressed 操作。 inputTransformer 函数用于在每次按下按钮时发送输入的当前值。

      您还可以订阅 viewModel 的单个 usernameValid / addressValid 属性,以便在此处向用户设置显示验证错误。

      【讨论】:

      • 我正在按照您的意图进行操作,但并未完全成功,但仍在尝试了解 CocoaAction 的工作方式。我认为这是迄今为止我发现的最好的方法。操作成功后,我会立即将此答案标记为正确。
      • 我喜欢将Action (github.com/ReactiveCocoa/ReactiveSwift/blob/master/Sources/…) 视为一种“信号生产工厂”。如果您熟悉 RAC 2.0,这相当于 RACCommand。您使用输入执行操作,然后返回一个 SignalProducer,该 SignalProducer 执行实际工作并将其结果作为事件发送。
      • CocoaAction 只是一个将动作连接到 UIKit 的包装器。我在示例中使用的变体使用inputTransformer 来确定每次执行操作时的实际输入。
      • 此外,Action 可以采用enabledIf 属性(在本例中为valid)。如果enabledIf 的值当前为假,这可确保您无法执行该操作。通过使用CocoaAction,此enabledIf 属性也会自动绑定到 UI,例如在示例中。如果 enabledIf 属性的值为 true,则 Button 已启用。
      猜你喜欢
      • 1970-01-01
      • 2018-01-27
      • 1970-01-01
      • 1970-01-01
      • 2019-09-01
      • 1970-01-01
      • 2017-07-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多