【发布时间】:2020-03-06 14:30:43
【问题描述】:
我有一个 UITextField 用于“创建帐户”场景中的用户 ID 字段。我希望用户 ID 只包含字母数字字符,没有任何空格。
我将我的视图控制器设置为 UITextFieldDelegate 并实现了 shouldChangeCharctersIn 函数(参见下面的代码),只为字母数字返回 true。我将我的控制器设置为用户名文本字段的代表。
一切都按预期进行除非涉及复制/粘贴或听写。在这种情况下,它几乎按预期工作。如果要插入的文本包含任何或非字母数字字符,则插入成功阻止,但插入单个空格字符除外。
一点 SO 和 Google 搜索让我明白我需要关闭 UITextField 的智能插入。所以我试着这样做。我在故事板编辑器中关闭了该字段的 SmartInsert 输入特征(见下图)。我通过在控制器的 viewDidAppear 期间检查 smartInsertDeleteType 属性验证了这实际上是如何实现的。
但什么都没有改变...
我在 shouldChangeCharctersIn 中添加了打印语句,以便我可以看到它何时被调用以及每次调用时返回的内容。当听写包含内部空格(例如“这是一个测试”)时,这正是在 replacementString 参数中传递给 shouldChangeCharctersIn 的内容。 shouldChangeCharctersIn 从未审查过插入以将此字符串与现有文本分开的前导空格字符。
除了将候选替换字符串记录到控制台之外,我还通过将候选字符串插入现有 UITextField 文本参数来创建结果字符串。在评估听写插入时,该空格似乎是在shouldChangeCharctersIn调用之前添加的,因为它出现在控制台输出中(例如“mikemayer67 This is a Test”) . *编辑:我在本文末尾添加了示例控制台输出。
我在这里缺少什么?
我不想在提交表单之前简单地执行空白清理,因为这可能会导致喜欢此方法引入的空格的用户感到困惑(即使他们无法手动输入它们)。我不喜欢必须弹出一个警报,提示他们需要纠正设备造成的问题的想法。
想法?
extension CreateAccountController : UITextFieldDelegate
{
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
{
guard let value = textField.text else { return false }
let testString = (value as NSString).replacingCharacters(in: range, with: string)
let rval = validate(textField,string:string)
print("allow: '\(string)' '\(testString)' ", (rval ? "OK" : "NOPE"))
return rval
}
func validate(_ textField: UITextField, string:String) -> Bool
{
var allowedCharacters = CharacterSet.alphanumerics
if textField == password1TextField || textField == password2TextField
{
allowedCharacters.insert(charactersIn: "-!:#$@.")
}
return string.rangeOfCharacter(from: allowedCharacters.inverted) == nil
}
}
allow: 'm' 'm' OK
allow: 'i' 'mi' OK
allow: 'k' 'mik' OK
allow: 'e' 'mike' OK
allow: ' ' 'mike ' NOPE
allow: 'm' 'mikem' OK
allow: 'a' 'mikema' OK
allow: 'y' 'mikemay' OK
allow: 'e' 'mikemaye' OK
allow: 'r' 'mikemayer' OK
allow: 'this is a test ' 'mike this is a test mayer' NOPE
编辑: 根据 DonMag 的建议,我创建了以下 UITextField 子类。它完全按照我的意愿处理键盘、听写和复制/粘贴输入。
@IBDesignable class LoginTextField: UITextField, UITextFieldDelegate
{
@IBInspectable var allowPasswordCharacters : Bool = false
var validatedText: String?
var dictationText: String?
override init(frame: CGRect)
{
super.init(frame: frame)
delegate = self
}
required init?(coder: NSCoder)
{
super.init(coder: coder)
delegate = self
}
// editing started, so save current text
func textFieldDidBeginEditing(_ textField: UITextField)
{
validatedText = text
dictationText = nil
}
// When dictation ends, the text property will be what we *expect*
// to show up if *shouldChangeCharactersIn* returns true
// Validate the dictated string and either cache it or reset it to
// the last validated text
override func dictationRecordingDidEnd()
{
dictationText = nil
if let t = text
{
let stripped = t.replacingOccurrences(of: " ", with: "")
if validate(string:stripped) {
dictationText = stripped
} else {
dictationText = validatedText
}
}
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
{
if let t = dictationText
{
// Handle change here, don't let UIKit do it
text = t
validatedText = t
dictationText = nil
}
else if let value = textField.text
{
let testString =
(value as NSString).replacingCharacters(in: range, with: string).replacingOccurrences(of: " ", with: "")
if validate(string:testString)
{
text = testString
validatedText = testString
}
}
return false
}
func validate(string:String) -> Bool
{
var allowedCharacters = CharacterSet.alphanumerics
if allowPasswordCharacters { allowedCharacters.insert(charactersIn: "-!:#$@.") }
return string.rangeOfCharacter(from: allowedCharacters.inverted) == nil
}
}
【问题讨论】:
-
我在发布这个问题后发现了 UITextPasteDelegate 协议。它解决了使用复制/粘贴时的问题,但没有解决听写问题。
-
在这种情况下您希望发生什么?你想清除归档吗?你想去掉空格吗?是否要将字段重置为用户开始听写之前的内容?
-
理想情况下,在唯一无效字符是空格的听写的情况下,将插入字符串并去除所有空格。如果听写中有非字母数字,则应阻止插入,并将字段恢复到听写尝试之前的状态。有一个帮助弹出窗口(使用信息按钮)概述了构成有效用户名的内容。我对其他建议持开放态度,但目前拒绝听写和添加空格的行为从用户体验来看是有问题的。
标签: ios uitextfield