Отображение ошибки проверки на iOS UITextField, аналогичной Android TextView.setError()

Есть ли способ показать ошибку проверки на UITextField, которая похожа на Android TextView.setError() в swift?

Ответ 1

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)

Ответ 2

Просто делюсь чем-то, что я часто использую. Это разработано, чтобы соответствовать "только нижней границе" TextFields. - Потому что они мне нравятся;) - но с легкостью можно настроить под любой стиль

Пример только нижней границы

Расширение для настройки текстового поля, чтобы показать только одну нижнюю строку:

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 для отображения только нижней границы в viewDidLoad:

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

Ответ 3

Вы можете проверить текст, установив делегат 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

Надеюсь, это поможет вам.

Ответ 4

Нет, вам нужно

  • подкласс UITextField

  • Создайте функцию setError, позвоните ей func setError()

  • В этой функции вы можете создать UIImageView, который содержит UIImage (изображение ошибки). Установите его вправо в UITextField с помощью UITextField.rightView

  • Не забудьте установить UITextField.rightViewMode, чтобы всегда показывать

EDIT:

В качестве альтернативы, если вы не предпочитаете подклассы. Вы можете прямо установить rightVIew UITextField на UIImageView, который содержит изображение с ошибкой

Ответ 5

Нет, для этого не существует встроенного метода. Для этого вам нужно настроить UITextField.

Для этого есть библиотека с открытым исходным кодом. Вы можете найти здесь: US2FormValidator

Ответ 6

После рабочего дня я сделал аналог TextView.setError() в Swift. Вот что я получил:

enter image description here

Код на Свифте 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 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
    }
}