在 iOS UITextField 上显示验证错误类似于 Android 的 TextView.setError()

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/30574484/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 06:09:42  来源:igfitidea点击:

Displaying validation error on iOS UITextField similar to Android's TextView.setError()

iosswiftuikit

提问by ihsan

Is there a way to show a validation error on a UITextFieldwhich is similar to Android's TextView.setError()in swift?

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

采纳答案by Shamas S - Reinstate Monica

UITextFielddoesn't come with a validation function out of the box. You can find some open source APIs to help you accomplish this. One possible option would be to look into the SSValidationTextFieldapi.

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

Code would be

代码将是

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)

回答by SpanTag

Just sharing something I use often. This is designed to fit for "bottom border only" TextFields. - Because I like them ;) - but can with ease be customized to fit whatever style

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

Bottom border only example

仅底部边框示例

Extension for setting up the text field to only show a single bottom line:

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

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

Then another extension to make it flash red and shake showing that there is a validation error:

然后另一个扩展使其闪烁红色并摇晃显示存在验证错误:

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

How to use:

如何使用:

Setup the UITextField to show only bottom border in viewDidLoad:

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

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

Then when some button is clicked and you do not validate the field:

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

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

回答by Victor Sigler

You can validate the text by setting the UITextFielddelegate to your view controller then do something like this :

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

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {

   // Your method to validate the input
   // self.validateInputText()

   return true
}

And you can even change its border color if you want:

如果需要,您甚至可以更改其边框颜色:

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

I hope this help you.

我希望这对你有帮助。

回答by Vergiliy

After a day of work, I made an analogue of TextView.setError()on Swift. Here's what I got:

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

enter image description here

在此处输入图片说明

Code on swift 5:

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

How to use:

如何使用:

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

回答by JayVDiyk

No, you need to

不,你需要

  1. subclass the UITextField

  2. Create a function that setError, let's call it func setError()

  3. In that function you can create an UIImageViewwhich contains an UIImage(error image). Set it to the rightView of UITextFieldby using UITextField.rightView

  4. Don't forget to set the UITextField.rightViewModeto always show
  1. 子类 UITextField

  2. 创建一个 setError 函数,让我们调用它 func setError()

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

  4. 不要忘记设置UITextField.rightViewMode为始终显示

EDIT:

编辑:

Alternatively, if you does not prefer subclassing. You could directly set the rightVIew of the UITextFieldto an UIImageViewthat holds an error image

或者,如果您不喜欢子类化。你可以的了rightVIew直接设置UITextFieldUIImageView保存错误图像

回答by Midhun MP

No, there is no built in method available for doing the same. For that you need to customize the UITextField.

不,没有可用于执行相同操作的内置方法。为此,您需要自定义UITextField.

There are some open-source library available for doing that. You can find one here : US2FormValidator

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