如何避免通过听写插入 UITextField 中不需要的额外空格
How do I avoid unwanted extra spaces inserted by dictation into UITextField
我有一个 UITextField 用于 "create account" 场景中的 userid 字段。我希望 userid 只包含字母数字字符而没有任何白色 space.
我将我的视图控制器设为 UITextFieldDelegate 并实现了 shouldChangeCharctersIn 函数(参见下面的代码),仅 return 对字母数字为真。我将我的控制器设置为用户名文本字段的委托。
一切都按预期工作除非copy/paste或涉及听写。在这种情况下,它 almost 按预期工作。如果要插入的文本包含任何或非字母数字字符,插入将被成功阻止,但插入单个 space 字符除外。
一点 SO 和 Google 搜索让我明白我需要关闭 UITextField 的智能插入。所以我试着这样做。我在故事板编辑器中关闭了此字段的 SmartInsert 输入特征(见下图)。我通过在控制器的 viewDidAppear 期间检查 smartInsertDeleteType 属性 来验证这确实发生了。
但是什么都没变...
我在 shouldChangeCharctersIn 中添加了打印语句,这样我就可以看到它何时被调用以及每次调用时 returning 是什么。当听写包含内部 whitespace(例如 "This is a test")时,这正是在 replacementString 参数中传递给 shouldChangeCharctersIn[=52= 的内容]. shouldChangeCharctersIn 从未审查过为将此字符串与现有文本分开而插入的前导 space 字符。
除了将候选替换字符串记录到控制台之外,我还通过将候选字符串插入现有 UITextField 文本参数来创建结果字符串。看起来这个白色 space 被添加到 prior 到 shouldChangeCharctersIn 的调用中,因为它在评估听写插入时出现在控制台输出中(例如 "mikemayer67 This is a Test")。 *编辑:我在此 post.
末尾添加了示例控制台输出
我在这里错过了什么?
我不想在提交表单之前简单地执行白色 space 的清理,因为这可能会让用户感到困惑,他们喜欢这种方法引入的 spaces(即使他们无法手动输入)。我也不喜欢必须弹出警报,提示他们需要更正设备造成的问题。
想法?
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 子类。它完全按照我的意愿处理键盘、听写和 copy/paste 输入。
@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
}
}
处理听写输入可能很棘手。
我不止一次被那个额外的space插入所困扰——那是我在其他应用程序中使用听写的时候……甚至没有谈论为它编写代码。
这可能对您有用,但您可能需要进行一些调整以增强它。例如,用户完成听写后,插入点移动到字符串的末尾。
我已经 subclassed UITextField
并在 class 中实现了所有验证和委托处理。您可以简单地通过添加一个新的 UITextField
并将其自定义 class 分配给 MyTextField
:
来尝试一下
class MyTextField: UITextField, UITextFieldDelegate {
var myCurText: String?
var myNewText: String?
var isDictation: Bool = false
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) {
// unwrap the text
if let t = text {
myCurText = t
}
}
// when dictation ends, the text will be what we *expect*
// e.g.
// text is "ABCD"
// insertion point is between the B and C
// user dictates "Test"
// text is now "ABTestCD"
// or
// user dictates "This is a test"
// text is now "ABThis is a testCD"
//
// So, we can validate the string and set a flag telling
// shouldChangeCharactersIn range not to do normal processing
override func dictationRecordingDidEnd() {
// set flag that we just dictated something
isDictation = true
// unwrap the text
if let t = text {
// just for debuggging
print("Dictation Ended: [\(t)]")
// strip spaces from the whole string
let stripped = t.replacingOccurrences(of: " ", with: "")
// validate the stripped string
if validate(self, string: stripped) {
// just for debugging
print("Valid! setting text to:", stripped)
// it's a valid string, so update myNewText
myNewText = stripped
} else {
// just for debugging
print("NOT setting text to:", stripped)
// it's NOT a valid string, so set myNewText to myCurText
myNewText = myCurText
}
}
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// if we just received a dictation
if isDictation {
// update self.text
text = myNewText
// update myCurText variable
myCurText = myNewText
// turn off the dictation flag
isDictation = false
// returning false from shouldChangeCharactersIn
return false
}
// we get here if it was NOT a result of dictation
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"))
if rval {
// if valid string, update myCurText variable
myCurText = testString
}
return rval
}
func validate(_ textField: UITextField, string:String) -> Bool
{
var allowedCharacters = CharacterSet.alphanumerics
allowedCharacters.insert(charactersIn: "-!:#$@.")
return string.rangeOfCharacter(from: allowedCharacters.inverted) == nil
}
}
如果它不能很好地完成工作,您可能需要阅读 Apple 的 UITextInput -> Using Dictation
文档
我有一个 UITextField 用于 "create account" 场景中的 userid 字段。我希望 userid 只包含字母数字字符而没有任何白色 space.
我将我的视图控制器设为 UITextFieldDelegate 并实现了 shouldChangeCharctersIn 函数(参见下面的代码),仅 return 对字母数字为真。我将我的控制器设置为用户名文本字段的委托。
一切都按预期工作除非copy/paste或涉及听写。在这种情况下,它 almost 按预期工作。如果要插入的文本包含任何或非字母数字字符,插入将被成功阻止,但插入单个 space 字符除外。
一点 SO 和 Google 搜索让我明白我需要关闭 UITextField 的智能插入。所以我试着这样做。我在故事板编辑器中关闭了此字段的 SmartInsert 输入特征(见下图)。我通过在控制器的 viewDidAppear 期间检查 smartInsertDeleteType 属性 来验证这确实发生了。
但是什么都没变...
我在 shouldChangeCharctersIn 中添加了打印语句,这样我就可以看到它何时被调用以及每次调用时 returning 是什么。当听写包含内部 whitespace(例如 "This is a test")时,这正是在 replacementString 参数中传递给 shouldChangeCharctersIn[=52= 的内容]. shouldChangeCharctersIn 从未审查过为将此字符串与现有文本分开而插入的前导 space 字符。
除了将候选替换字符串记录到控制台之外,我还通过将候选字符串插入现有 UITextField 文本参数来创建结果字符串。看起来这个白色 space 被添加到 prior 到 shouldChangeCharctersIn 的调用中,因为它在评估听写插入时出现在控制台输出中(例如 "mikemayer67 This is a Test")。 *编辑:我在此 post.
末尾添加了示例控制台输出我在这里错过了什么?
我不想在提交表单之前简单地执行白色 space 的清理,因为这可能会让用户感到困惑,他们喜欢这种方法引入的 spaces(即使他们无法手动输入)。我也不喜欢必须弹出警报,提示他们需要更正设备造成的问题。
想法?
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 子类。它完全按照我的意愿处理键盘、听写和 copy/paste 输入。
@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
}
}
处理听写输入可能很棘手。
我不止一次被那个额外的space插入所困扰——那是我在其他应用程序中使用听写的时候……甚至没有谈论为它编写代码。
这可能对您有用,但您可能需要进行一些调整以增强它。例如,用户完成听写后,插入点移动到字符串的末尾。
我已经 subclassed UITextField
并在 class 中实现了所有验证和委托处理。您可以简单地通过添加一个新的 UITextField
并将其自定义 class 分配给 MyTextField
:
class MyTextField: UITextField, UITextFieldDelegate {
var myCurText: String?
var myNewText: String?
var isDictation: Bool = false
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) {
// unwrap the text
if let t = text {
myCurText = t
}
}
// when dictation ends, the text will be what we *expect*
// e.g.
// text is "ABCD"
// insertion point is between the B and C
// user dictates "Test"
// text is now "ABTestCD"
// or
// user dictates "This is a test"
// text is now "ABThis is a testCD"
//
// So, we can validate the string and set a flag telling
// shouldChangeCharactersIn range not to do normal processing
override func dictationRecordingDidEnd() {
// set flag that we just dictated something
isDictation = true
// unwrap the text
if let t = text {
// just for debuggging
print("Dictation Ended: [\(t)]")
// strip spaces from the whole string
let stripped = t.replacingOccurrences(of: " ", with: "")
// validate the stripped string
if validate(self, string: stripped) {
// just for debugging
print("Valid! setting text to:", stripped)
// it's a valid string, so update myNewText
myNewText = stripped
} else {
// just for debugging
print("NOT setting text to:", stripped)
// it's NOT a valid string, so set myNewText to myCurText
myNewText = myCurText
}
}
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// if we just received a dictation
if isDictation {
// update self.text
text = myNewText
// update myCurText variable
myCurText = myNewText
// turn off the dictation flag
isDictation = false
// returning false from shouldChangeCharactersIn
return false
}
// we get here if it was NOT a result of dictation
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"))
if rval {
// if valid string, update myCurText variable
myCurText = testString
}
return rval
}
func validate(_ textField: UITextField, string:String) -> Bool
{
var allowedCharacters = CharacterSet.alphanumerics
allowedCharacters.insert(charactersIn: "-!:#$@.")
return string.rangeOfCharacter(from: allowedCharacters.inverted) == nil
}
}
如果它不能很好地完成工作,您可能需要阅读 Apple 的 UITextInput -> Using Dictation
文档