在 iOS UITextField 上显示类似于 Android 的 TextView.setError() 的验证错误
Displaying validation error on iOS UITextField similar to Android's TextView.setError()
有没有办法在 UITextField
上显示验证错误,类似于 Android 在 swift 中的 TextView.setError()
?
您可以通过将 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
希望对你有所帮助。
不,没有可用于执行相同操作的内置方法。为此,您需要自定义 UITextField
.
有一些开源库可用于执行此操作。你可以在这里找到一个:US2FormValidator
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)
不,你需要
子类 UITextField
创建一个设置错误的函数,我们称之为func setError()
在该函数中,您可以创建一个包含 UIImage
(错误图像)的 UIImageView
。使用UITextField.rightView
设置到UITextField
的rightView
- 不要忘记将
UITextField.rightViewMode
设置为始终显示
编辑:
或者,如果您不喜欢子类化。您可以直接将 UITextField
的 rightVIew 设置为包含错误图像
的 UIImageView
只是分享一些我经常使用的东西。这是为 "bottom border only" TextFields 设计的。 - 因为我喜欢它们 ;) - 但可以轻松定制以适应任何风格
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 以在 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)
}
}
经过一天的工作,我在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
}
}
这是我在 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。
或者,您可能想创建自己的结构体 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))
)
)
}
}
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 这样的错误消息。
移除 RightView
textField.addRightView(rightView: "")
添加 RightView
textField.addRightView(rightView: "rightVwWarning", tintColor: UIColor.red)
将 RightView 和错误消息添加为 android
textField.addRightView(rightView: "rightVwWarning", tintColor: UIColor.red, errorMessage: "Error")
有没有办法在 UITextField
上显示验证错误,类似于 Android 在 swift 中的 TextView.setError()
?
您可以通过将 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
希望对你有所帮助。
不,没有可用于执行相同操作的内置方法。为此,您需要自定义 UITextField
.
有一些开源库可用于执行此操作。你可以在这里找到一个:US2FormValidator
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)
不,你需要
子类
UITextField
创建一个设置错误的函数,我们称之为
func setError()
在该函数中,您可以创建一个包含
UIImage
(错误图像)的UIImageView
。使用UITextField.rightView
设置到- 不要忘记将
UITextField.rightViewMode
设置为始终显示
UITextField
的rightView
编辑:
或者,如果您不喜欢子类化。您可以直接将 UITextField
的 rightVIew 设置为包含错误图像
UIImageView
只是分享一些我经常使用的东西。这是为 "bottom border only" TextFields 设计的。 - 因为我喜欢它们 ;) - 但可以轻松定制以适应任何风格
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 以在 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)
}
}
经过一天的工作,我在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
}
}
这是我在 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。
或者,您可能想创建自己的结构体 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))
)
)
}
}
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 这样的错误消息。
移除 RightView
textField.addRightView(rightView: "")
添加 RightView
textField.addRightView(rightView: "rightVwWarning", tintColor: UIColor.red)
将 RightView 和错误消息添加为 android
textField.addRightView(rightView: "rightVwWarning", tintColor: UIColor.red, errorMessage: "Error")