如何避免通过听写插入到 UITextField 中的不需要的额外空格 [英] How do I avoid unwanted extra spaces inserted by dictation into UITextField

查看:24
本文介绍了如何避免通过听写插入到 UITextField 中的不需要的额外空格的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在创建帐户"方案中,我有一个用于用户 ID 字段的 UITextField.我希望用户 ID 只包含字母数字字符,没有任何空格.

I have a UITextField for the userid field in a "create account" scenario. I want the userid to only contain alphanumeric characters without any whitespace.

我让我的视图控制器成为 UITextFieldDelegate 并实现了 shouldChangeCharctersIn 函数(见下面的代码),只对字母数字返回 true.我将我的控制器设置为用户名文本字段的代理.一切都按预期工作除非 复制/粘贴或 涉及听写.在这种情况下,它几乎按预期工作.如果要插入的文本包含任何或非字母数字字符,则插入成功阻止,除了插入单个空格字符.

I made my view controller to be a UITextFieldDelegate and implemented the shouldChangeCharctersIn function (see code below) to only return true for alphanumerics. I set my controller to be the delegate for the username text field. Everything works as expected unless copy/paste or dictation is involved. In this case, it almost works as expected. If the text to be inserted contains any or non-alphanumeric characters, the insertion is successfully blocked except for the insertion of a single space character.

一点 SO 和谷歌搜索让我明白我需要关闭 UITextField 的智能插入.所以我尝试这样做.我在故事板编辑器中关闭了该字段的 SmartInsert 输入特征(见下图).我通过在控制器的 viewDidAppear 期间检查 smartInsertDeleteType 属性来验证这实际上是通过的.

A little SO and Google searching led me to understand that I need to turn off smart insertion for the UITextField. So I tried to do that. I turned off the SmartInsert input trait (see image below) for this field in the storyboard editor. I verified that this actually took by checking the smartInsertDeleteType property during the controller's viewDidAppear.

但没有任何改变......

我在 shouldChangeCharctersIn 中添加了打印语句,以便我可以看到它何时被调用以及它在每次调用时返回什么.当听写包含内部空格(例如这是一个测试")时,这正是在 replacementString 参数中传递给 shouldChangeCharctersIn 的内容.shouldChangeCharctersIn 从未审查过插入以将该字符串与现有文本分开的前导空格字符.

I added print statements into shouldChangeCharctersIn so that I could see when it is being invoked and what it is returning on each invocation. When dictation contains internal whitespace (e.g. "This is a test"), that is exactly what is passed in the replacementString parameter to shouldChangeCharctersIn. The leading space character that was inserted to separate this string from the existing text was never vetted by shouldChangeCharctersIn.

除了将候选替换字符串记录到控制台之外,我还通过将候选字符串插入到现有 UITextField 文本参数中来创建结果字符串.这个空白似乎是在之前添加到对 shouldChangeCharctersIn 的调用中的,因为它在评估听写插入时出现在控制台输出中(例如mikemayer67 This is a Test").*我在本文末尾添加了示例控制台输出.

In addition to logging the candidate replacement string to the console, I created the resultant string from inserting the candidate string into the existing UITextField text parameter. It appears that this white space was added prior to the call to shouldChangeCharctersIn as it appears in the console output when evaluating the dictation insertion (e.g. "mikemayer67 This is a Test"). * I added sample console output at the end of this post.

我在这里遗漏了什么?

我不想在提交表单之前简单地清除空格,因为这可能会导致喜欢这种方法引入的空格的用户感到困惑(即使他们无法手动输入它们).我不喜欢必须弹出警报,提示他们需要纠正由设备造成的问题.

I don't want to simply perform a cleanup of whitespace before submitting the form as this may lead to a confused user who likes the spaces introduced by this method (even though they cannot enter them manually). I don't like either the idea of having to pop up an alert that they need to correct a problem that was created by the device.

想法?

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 子类.它完全按照我的意愿处理键盘、听写和复制/粘贴输入.


Based on the suggestion by DonMag, I created the following UITextField subclass. It handles keyboard, dictation, and copy/paste entry exactly as I would like.

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

推荐答案

处理听写输入可能很棘手.

Dealing with dictation input can be tricky.

我不止一次被那个额外的空间插入所困扰 - 那就是我在其他应用程序中使用听写的时候......甚至没有谈论为它编写代码.

I've been burned more than once by that extra-space insertion - and that's just when I'm using dictation in other apps... not even talking about writing code for it.

这可能对您有用,尽管您可能需要进行一些调整以增强它.例如,在用户完成听写后,插入点移动到字符串的末尾.

This might work for you, although you may want to do some tweaking to enhance it. For example, after the user finishes dictation, the insertion point moves to the end of the string.

我已经继承了 UITextField 并在类中实现了所有验证和委托处理.您可以简单地通过添加一个新的 UITextField 并将其自定义类分配给 MyTextField 来尝试一下:

I've subclassed UITextField and implemented all the validation and delegate handling inside the class. You can try it out simply by adding a new UITextField and assigning its custom class to 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 -> 使用听写

If it doesn't quite do the job, you may want to take a read through Apple's docs for UITextInput -> Using Dictation

这篇关于如何避免通过听写插入到 UITextField 中的不需要的额外空格的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆