【问题标题】:Displaying validation error on iOS UITextField similar to Android's TextView.setError()在 iOS UITextField 上显示验证错误,类似于 Android 的 TextView.setError()
【发布时间】:2015-08-14 23:17:56
【问题描述】:

有没有办法在 UITextField 上显示验证错误,这类似于 Android 的 TextView.setError() swift?

【问题讨论】:

    标签: ios swift uikit


    【解决方案1】:

    只是分享一些我经常使用的东西。这旨在适合“仅底部边框”文本字段。 - 因为我喜欢它们 ;) - 但可以轻松定制以适应任何风格

    Bottom border only example

    将文本字段设置为仅显示一条底线的扩展:

    extension UITextField {
        func setBottomBorderOnlyWith(color: CGColor) {
            self.borderStyle = .none            
            self.layer.masksToBounds = false
            self.layer.shadowColor = color
            self.layer.shadowOffset = CGSize(width: 0.0, height: 1.0)
            self.layer.shadowOpacity = 1.0
            self.layer.shadowRadius = 0.0
        }
    }
    

    然后另一个扩展使其闪烁红色并晃动表明存在验证错误:

    extension UITextField {
        func isError(baseColor: CGColor, numberOfShakes shakes: Float, revert: Bool) {
            let animation: CABasicAnimation = CABasicAnimation(keyPath: "shadowColor")
            animation.fromValue = baseColor
            animation.toValue = UIColor.red.cgColor
            animation.duration = 0.4
            if revert { animation.autoreverses = true } else { animation.autoreverses = false }
            self.layer.add(animation, forKey: "")
    
            let shake: CABasicAnimation = CABasicAnimation(keyPath: "position")
            shake.duration = 0.07
            shake.repeatCount = shakes
            if revert { shake.autoreverses = true  } else { shake.autoreverses = false }
            shake.fromValue = NSValue(cgPoint: CGPoint(x: self.center.x - 10, y: self.center.y))
            shake.toValue = NSValue(cgPoint: CGPoint(x: self.center.x + 10, y: self.center.y))
            self.layer.add(shake, forKey: "position")
        }
    }
    

    使用方法:

    设置 UITextField 在 vi​​ewDidLoad 中只显示底部边框:

    override func viewDidLoad() {
        myTextField.setBottomBorderOnlyWith(color: UIColor.gray.cgColor)
    }
    

    然后当单击某个按钮并且您不验证该字段时:

    @IBAction func someButtonIsClicked(_ sender: Any) {
        if let someValue = myTextField, !name.isEmpty {
            // Good To Go!
        } else {
            myTextField.isError(baseColor: UIColor.gray.cgColor, numberOfShakes: 3, revert: true)
        }
    }
    

    【讨论】:

    • 完美答案 ;)
    • 很好的答案,但是除了警报还有什么方法可以显示一些消息?
    • me gustaria poder agregar algun texto
    • 解决方案很好,但如何显示错误标签...
    【解决方案2】:

    您可以通过将UITextField 委托设置为您的视图控制器来验证文本,然后执行以下操作:

    func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
    
       // Your method to validate the input
       // self.validateInputText()
    
       return true
    }
    

    如果你愿意,你甚至可以改变它的边框颜色:

    textField.layer.borderColor = UIColor.redColor().CGColor
    

    希望对你有所帮助。

    【讨论】:

      【解决方案3】:

      经过一天的工作,我在 Swift 上做了一个 TextView.setError() 的类比。这是我得到的:

      swift 5 上的代码:

      import UIKit
      
      private var rightViews = NSMapTable<UITextField, UIView>(keyOptions: NSPointerFunctions.Options.weakMemory, valueOptions: NSPointerFunctions.Options.strongMemory)
      private var errorViews = NSMapTable<UITextField, UIView>(keyOptions: NSPointerFunctions.Options.weakMemory, valueOptions: NSPointerFunctions.Options.strongMemory)    
      
      extension UITextField {
          // Add/remove error message
          func setError(_ string: String? = nil, show: Bool = true) {
              if let rightView = rightView, rightView.tag != 999 {
                  rightViews.setObject(rightView, forKey: self)
              }
      
              // Remove message
              guard string != nil else {
                  if let rightView = rightViews.object(forKey: self) {
                      self.rightView = rightView
                      rightViews.removeObject(forKey: self)
                  } else {
                      self.rightView = nil
                  }
      
                  if let errorView = errorViews.object(forKey: self) {
                      errorView.isHidden = true
                      errorViews.removeObject(forKey: self)
                  }
      
                  return
              }
      
              // Create container
              let container = UIView()
              container.translatesAutoresizingMaskIntoConstraints = false
      
              // Create triangle
              let triagle = TriangleTop()
              triagle.backgroundColor = .clear
              triagle.translatesAutoresizingMaskIntoConstraints = false
              container.addSubview(triagle)
      
              // Create red line
              let line = UIView()
              line.backgroundColor = .red
              line.translatesAutoresizingMaskIntoConstraints = false
              container.addSubview(line)
      
              // Create message
              let label = UILabel()
              label.text = string
              label.textColor = .white
              label.numberOfLines = 0
              label.font = UIFont.systemFont(ofSize: 15)
              label.backgroundColor = .black
              label.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 250), for: .horizontal)
              label.translatesAutoresizingMaskIntoConstraints = false
              container.addSubview(label)
      
              // Set constraints for triangle
              triagle.heightAnchor.constraint(equalToConstant: 10).isActive = true
              triagle.widthAnchor.constraint(equalToConstant: 15).isActive = true
              triagle.topAnchor.constraint(equalTo: container.topAnchor, constant: -10).isActive = true
              triagle.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -15).isActive = true
      
              // Set constraints for line
              line.heightAnchor.constraint(equalToConstant: 3).isActive = true
              line.topAnchor.constraint(equalTo: triagle.bottomAnchor, constant: 0).isActive = true
              line.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 0).isActive = true
              line.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: 0).isActive = true
      
              // Set constraints for label
              label.topAnchor.constraint(equalTo: line.bottomAnchor, constant: 0).isActive = true
              label.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: 0).isActive = true
              label.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 0).isActive = true
              label.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: 0).isActive = true
      
              if !show {
                  container.isHidden = true
              }
              // superview!.superview!.addSubview(container)
              UIApplication.shared.keyWindow!.addSubview(container)
      
              // Set constraints for container
              container.widthAnchor.constraint(lessThanOrEqualTo: superview!.widthAnchor, multiplier: 1).isActive = true
              container.trailingAnchor.constraint(equalTo: superview!.trailingAnchor, constant: 0).isActive = true
              container.topAnchor.constraint(equalTo: superview!.bottomAnchor, constant: 0).isActive = true
      
              // Hide other error messages
              let enumerator = errorViews.objectEnumerator()
              while let view = enumerator!.nextObject() as! UIView? {
                  view.isHidden = true
              }
      
              // Add right button to textField
              let errorButton = UIButton(type: .custom)
              errorButton.tag = 999
              errorButton.setImage(UIImage(named: "ic_error"), for: .normal)
              errorButton.frame = CGRect(x: 0, y: 0, width: frame.size.height, height: frame.size.height)
              errorButton.addTarget(self, action: #selector(errorAction), for: .touchUpInside)
              rightView = errorButton
              rightViewMode = .always
      
              // Save view with error message
              errorViews.setObject(container, forKey: self)
          }
      
          // Show error message
          @IBAction
          func errorAction(_ sender: Any) {
              let errorButton = sender as! UIButton
              let textField = errorButton.superview as! UITextField
      
              let errorView = errorViews.object(forKey: textField)
              if let errorView = errorView {
                  errorView.isHidden.toggle()
              }
      
              let enumerator = errorViews.objectEnumerator()
              while let view = enumerator!.nextObject() as! UIView? {
                  if view != errorView {
                      view.isHidden = true
                  }
              }
      
              // Don't hide keyboard after click by icon
              UIViewController.isCatchTappedAround = false
          }
      }
      
      class TriangleTop: UIView {
          override init(frame: CGRect) {
              super.init(frame: frame)
          }
      
          required init?(coder aDecoder: NSCoder) {
              super.init(coder: aDecoder)
          }
      
          override func draw(_ rect: CGRect) {
              guard let context = UIGraphicsGetCurrentContext() else {
                  return
              }
      
              context.beginPath()
              context.move(to: CGPoint(x: (rect.maxX / 2.0), y: rect.minY))
              context.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
              context.addLine(to: CGPoint(x: (rect.minX / 2.0), y: rect.maxY))
              context.closePath()
      
              context.setFillColor(UIColor.red.cgColor)
              context.fillPath()
          }
      }
      
      

      使用方法:

      class MyViewController: UIViewController {
          @IBOutlet weak var emailField: UITextField!
      
          override func viewDidLoad() {
              super.viewDidLoad()
      
              emailField.delegate = self
          }
      }
      
      // Validation textFields
      extension MyViewController: UITextFieldDelegate {
          // Remove error message after start editing
          func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
              textField.setError()
              return true
          }
      
          // Check error
          func textFieldDidBeginEditing(_ textField: UITextField) {
              Validator.isValidEmail(field: textField)
          }
      
          // Check error
          func textFieldDidEndEditing(_ textField: UITextField) {
              Validator.isValidEmail(field: textField, show: false)
          }
      }
      
      class Validator {
          static let EMAIL_ADDRESS =
              "[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" +
              "\\@" +
              "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
              "(" +
                  "\\." +
                  "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" +
              ")+"
      
          // Validator e-mail from string
          static func isValidEmail(_ value: String) -> Bool {
              let string = value.trimmingCharacters(in: .whitespacesAndNewlines)
              let predicate = NSPredicate(format: "SELF MATCHES %@", Validator.EMAIL_ADDRESS)
              return predicate.evaluate(with: string) || string.isEmpty
          }
      
          static func isValidEmail(field: UITextField, show: Bool = true) -> Bool {
              if Validator.isValidEmail(field.text!) {
                  field.setError()
                  return true
              } else {
                  field.setError("Error message", show: show)
              }
              return false
          }
      }
      

      【讨论】:

      • 谢谢你 - 我正在寻找类似的东西 - 但是当我使用上面的错误时,错误低于屏幕边界 - 我只能在屏幕底部看到一个红色三角形:(跨度>
      • @Cathal 在我的情况下,由于按钮位于页面底部,这不是问题。在你的情况下,当显示错误时,我会增加窗口的高度,当隐藏时,会再次降低它。
      • @BrunoSosaFastTag 该功能在发布应用中实现。对我来说一切正常。
      • @Vergiliy 错误显示在整个视图下方如何适应始终在框下方?
      • Type 'UIViewController' has no member 'isCatchTappedAround' ...我收到此错误
      【解决方案4】:

      不,你需要

      1. 继承UITextField

      2. 创建一个setError函数,我们称之为func setError()

      3. 在该函数中,您可以创建一个 UIImageView,其中包含一个 UIImage(错误图像)。使用UITextField.rightView将其设置为UITextField的rightView

      4. 别忘了将UITextField.rightViewMode 设置为始终显示

      编辑:

      或者,如果您不喜欢子类化。您可以直接将UITextField 的 rightVIew 设置为包含错误图像的UIImageView

      【讨论】:

        【解决方案5】:

        UITextField 没有开箱即用的验证功能。您可以找到一些开源 API 来帮助您完成此任务。一种可能的选择是查看SSValidationTextField api。

        代码是

        var phoneValidationTextField = SSValidationTextField(frame: CGRectMake(200, 200, 150, 50))
        phoneValidationTextField.validityFunction = self.isValidPhone
        phoneValidationTextField.delaytime = 0.5
        phoneValidationTextField.errorText = "Incorrect Format"
        phoneValidationTextField.successText = "Valid Format"
        phoneValidationTextField.borderStyle = UITextBorderStyle.RoundedRect
        self.addSubview(phoneValidationTextField)
        

        【讨论】:

          【解决方案6】:
          extension UITextField {
          
          
          /**
          this function adds a right view on the text field
          */
          
          
          func addRightView(rightView: String, tintColor: UIColor? = nil, errorMessage: String? = nil) {
              if rightView != "" {
                  let rightview = UIButton(type: .custom)
                  
                  if tintColor != nil {
                      let templateImage = UIImage(named: rightView)?.withRenderingMode(UIImage.RenderingMode.alwaysTemplate)
                      rightview.setImage(templateImage, for: .normal)
                      rightview.tintColor = tintColor
                  }
                  else{
                      rightview.setImage(UIImage(named: rightView), for: .normal)
                  }
                  
                  if let message = errorMessage {
                      rightview.imageEdgeInsets = UIEdgeInsets(top: 0, left: -16, bottom: 5, right: 0)
                      showErrorView(errorMessage: message)
                  } else {
                      rightview.imageEdgeInsets = UIEdgeInsets(top: 0, left: -16, bottom: 0, right: 0)
                  }
                  
                  self.rightViewMode = .always
                  self.rightView = rightview
              }
              else{
                  self.rightView = .none
                  for vw in self.subviews where vw.tag == 1000 {
                      vw.removeFromSuperview()
                  }
              }
          }
          
          /**
           this function add custom alert as a right view on the text field
           */
          
          private func showErrorView(errorMessage: String) {
              
              let containerVw = UIView(frame: CGRect(x: self.frame.origin.x + 30, y: 30, width: self.frame.size.width - 60, height: 45))
              containerVw.backgroundColor = .clear
              containerVw.tag = 1000
              
              let triangleVw = UIButton(frame: CGRect(x: containerVw.frame.maxX - 25, y: 0, width: 15, height: 15))
              triangleVw.isUserInteractionEnabled = false
              triangleVw.setImage(UIImage(named: "arrowUp"), for: .normal)
              triangleVw.imageEdgeInsets = UIEdgeInsets(top: 3, left: 0, bottom: 0, right: 0)
              triangleVw.tintColor = AppColor.red1
              
              let messageVw = UIView(frame: CGRect(x: containerVw.frame.origin.x, y: triangleVw.frame.maxY - 2, width: containerVw.frame.width, height: 30))
              messageVw.backgroundColor = UIColor.red
              
              let errorLbl = UILabel(frame: CGRect(x: 0, y: 2, width: messageVw.frame.size.width, height: messageVw.frame.size.height - 2))
              errorLbl.backgroundColor = .black
              errorLbl.numberOfLines = 2
              messageVw.addSubview(errorLbl)
              errorLbl.text = errorMessage
              errorLbl.textColor = .white
              errorLbl.textAlignment = .left
              errorLbl.font = UIFont.systemFont(ofSize: 14)
              
              containerVw.addSubview(triangleVw)
              containerVw.sendSubviewToBack(triangleVw)
              containerVw.addSubview(messageVw)
              containerVw.layoutIfNeeded()
              
              self.addSubview(containerVw)
              self.bringSubviewToFront(containerVw)
          }
          }
          

          用法:

          rightView 是根据您的要求传递的图像名称。如果要删除右视图,请设置为空。

          tintColor 是可选的,可以根据您的要求使用任何一个。

          设置消息(errorMessage) 值,如果您想显示类似 android 的错误消息。

          删除右视图

          textField.addRightView(rightView: "")
          

          添加右视图

          textField.addRightView(rightView: "rightVwWarning", tintColor: UIColor.red)
          

          将 RightView 和错误消息添加为 android

          textField.addRightView(rightView: "rightVwWarning", tintColor: UIColor.red, errorMessage: "Error")
          

          【讨论】:

            【解决方案7】:

            不,没有内置方法可以做同样的事情。为此,您需要自定义 UITextField

            有一些开源库可用于执行此操作。你可以在这里找到:US2FormValidator

            【讨论】:

              【解决方案8】:

              这与我在 Android 中看到的最接近。在 SwiftUI 5 中测试

              第一步,让我们定义一些变量:

              @State editText = ""
              @State editTextError : String? = nil
              
              let editFontSize = 18   
              let editTextPadding = 2 // this depends on TextFieldStyle, 5 works well for default, for others you might need to check
              let errorColor = Color(UIColor(red:1,green:0,blue:0, alpha:0.5))
              let normalColor = Color(UIColor(red:0,green:0,blue:0, alpha:0.1))
              

              第二步,修改我们的TextField

                          TextField<Text>("text hint", text: self.$editText, onEditingChanged: self.onEdit, onCommit: self.onCommit)
                              .disableAutocorrection(true)
                              .autocapitalization(.none)
                              .frame(maxWidth: .infinity, alignment:.topLeading)
                              .font(Font(UIFont.systemFont(ofSize: CGFloat(editFontSize))))
                              .border(editTextError == nil ? normalColor : errorColor)
                              .overlay(editTextError == nil ? AnyView(EmptyView()) :
                                  AnyView(
                                  Text(editTextError!)
                                      .offset(y:CGFloat(editFontSize + 2*editTextPadding))
                                      .foregroundColor(Color.orange)
                                      .frame(maxWidth: .infinity, alignment: .leading)
                                      .multilineTextAlignment(TextAlignment.leading)
                                      .font(Font(UIFont.preferredFont(forTextStyle:.caption1)))
                                      )
                              )
              

              最后,我们需要定义 onCommit(你也可以在 onEdit 中做)

              func onSubmit() {
                 editTextError = validateText(editText)
              }
              

              其中 validateText() 是您的自定义文本字段验证函数,如果未发现错误,则返回 nil,否则返回错误消息。

              或者,您可能想创建自己的 struct TextField,然后按如下方式使用它:

                          EditBox("hint", text: self.$text, onEditingChanged: self.onEdit,
                                          onCommit: onCommitAccount, error: self.$textError, textSize: editFontSize,
                                          textPadding: editTextPadding)
                              .disableAutocorrection(true)
                              .autocapitalization(.none)
                              .frame(maxWidth: .infinity, alignment:.topLeading)
                              .font(Font(UIFont.boldSystemFont(ofSize: CGFloat(editFontSize))))
                              .textFieldStyle(RoundedBorderTextFieldStyle())
              

              其中EditBox定义如下:

              struct EditBox: View {
              
              @Binding<String?> var error: String?
              @Binding<String> var text: String
              var title : String
              var onEditingChanged : (Bool) -> Void
              var onCommit : () -> Void
              var errorColor : Color
              var normalColor : Color
              var textSize : Int
              var textPadding : Int
              var aboveText : Bool
              
              public init(_ title: String, text: Binding<String>, onEditingChanged: @escaping (Bool) -> Void = { _ in },
                  onCommit: @escaping () -> Void = {},
                  error: Binding<String?>,
                  errorColor: Color = Color(UIColor(red:1,green:0,blue:0, alpha:0.5)),
                  normalColor: Color = Color(UIColor(red:0,green:0,blue:0, alpha:0.1)),
                  textSize : Int = 18,
                  textPadding : Int = 5, // This is tested for RoundedTextFieldStyle, for others you might need to change
                  aboveText: Bool = false
                  )  {
              
                  self._text = text
                  self._error = error
              
                  self.title = title
                  self.onEditingChanged = onEditingChanged
                  self.onCommit = onCommit
                  self.errorColor = errorColor
                  self.normalColor = normalColor
                  self.textSize = textSize
                  self.textPadding = textPadding
                  self.aboveText = aboveText
              }
              
              var body : some View {
                  
                  let offset = aboveText ? (0 - textSize - 2*textPadding) : (textSize + 2*textPadding)
                  
                  return TextField<Text> (title, text: $text, onEditingChanged: onEditingChanged, onCommit: onCommit)
                      .border(self.error  != nil ? self.errorColor : self.normalColor)
                      .overlay(self.error == nil ? AnyView(EmptyView()) :
                          AnyView(
                              Text(error!)
                                  .foregroundColor(Color.orange)
                                  .frame(maxWidth: .infinity, alignment: .leading)
                                  .multilineTextAlignment(.leading)
                                  .font(Font(UIFont.preferredFont(forTextStyle:.caption1)))
                                  .offset(y:CGFloat(offset))
                          )
                      )
              }
              

              }

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2017-07-12
                • 1970-01-01
                • 2011-11-18
                • 2013-05-31
                • 2010-10-25
                相关资源
                最近更新 更多