在 UITextField 中检测退格事件

Detect backspace Event in UITextField

我正在寻找有关如何捕获退格事件的解决方案,大多数 Stack Overflow 答案都在 Objective-C 中,但我需要 Swift 语言。

首先,我为 UITextField 设置了委托并将其设置为 self

self.textField.delegate = self;

然后我知道使用 shouldChangeCharactersInRange 委托方法来检测是否按下了退格键,所有代码都在 Objective-C 中。我需要在 Swift 中使用以下这些方法。

-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    const char * _char = [string cStringUsingEncoding:NSUTF8StringEncoding];
    int isBackSpace = strcmp(_char, "\b");

    if (isBackSpace == -8) {
        // NSLog(@"Backspace was pressed");
    }

    return YES;
}

Swift 4.2

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
    if let char = string.cString(using: String.Encoding.utf8) {
        let isBackSpace = strcmp(char, "\b")
        if (isBackSpace == -92) {
            print("Backspace was pressed")
        }
    }
    return true
}

旧 Swift 版本

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
    let  char = string.cStringUsingEncoding(NSUTF8StringEncoding)!
    let isBackSpace = strcmp(char, "\b")

    if (isBackSpace == -92) {
        println("Backspace was pressed")
    }
    return true
}

试试这个

public func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {

if(string == "") {

        print("Backspace pressed");
        return true;
    }
}

注意:如果要允许退格,可以 return "true"。否则你可以 return "false".

如果您甚至需要在空文本字段中检测退格键(例如,如果您需要在按下退格键时自动切换回上一个文本字段),您可以使用建议方法的组合 - 添加不可见符号并使用标准委托方法 textField:shouldChangeCharactersInRange:replacementString: 点赞关注

  1. 创建隐形标志

    private struct Constants {
        static let InvisibleSign = "\u{200B}"
    }
    
  2. 为文本字段设置委托

    textField.delegate = self
    
  3. On event EditingChanged 检查文本,如果需要添加不可见符号,如下所示:

    @IBAction func didChangeEditingInTextField(sender: UITextField) {
        if var text = sender.text {
            if text.characters.count == 1 && text != Constants.InvisibleSign {
                text = Constants.InvisibleSign.stringByAppendingString(text)
                sender.text = text
            }
        }
    }
    

  1. 添加委托方法的实现textField:shouldChangeCharactersInRange:replacementString:

    extension UIViewController : UITextFieldDelegate {
        // MARK: - UITextFieldDelegate
        func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
    
            let  char = string.cStringUsingEncoding(NSUTF8StringEncoding)!
            let isBackSpace = strcmp(char, "\b")
    
            if (isBackSpace == -92) {
                if var string = textField.text {
                    string = string.stringByReplacingOccurrencesOfString(Constants.InvisibleSign, withString: "")
                    if string.characters.count == 1 {
                        //last visible character, if needed u can skip replacement and detect once even in empty text field
                        //for example u can switch to prev textField 
                        //do stuff here                            
                    }
                }
            }
            return true
        }
    }
    

在Swift3

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
    let  char = string.cString(using: String.Encoding.utf8)!
    let isBackSpace = strcmp(char, "\b")

    if (isBackSpace == -92) {
         print("Backspace was pressed")
    }
    return true
}

:)

Swift 4: 如果用户按下退格按钮,字符串为空,所以这种方法强制 textField 只接受来自指定字符集的字符(在此case utf8 字符)和退格键(string.isEmpty case)。

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    if string.cString(using: String.Encoding.utf8) != nil {
        return true
    } else if string.isEmpty {
        return true
    } else {
        return false
    }
}

我更喜欢子类化 UITextField 和覆盖 deleteBackward() 因为这比使用 shouldChangeCharactersInRange:

的 hack 更可靠
class MyTextField: UITextField {
    override public func deleteBackward() {
        if text == "" {
             // do something when backspace is tapped/entered in an empty text field
        }
        // do something for every backspace
        super.deleteBackward()
    }
}

shouldChangeCharactersInRange hack 与放置在文本字段中的不可见字符相结合有几个缺点:

  • 连接键盘后,可以将光标放在不可见字符之前,并且不再检测到退格键,
  • 用户甚至可以 select 那个不可见的字符(使用 Shift Arrow 在键盘上,甚至通过点击插入符号)并且会对那个奇怪的字符感到困惑,
  • 只要只有这个不可见的字符,自动完成栏就会提供奇怪的选择,
  • 具有基于文本字段文本的候选选项的亚洲语言键盘将会混淆,
  • placeholder 不再显示,
  • 即使 clearButtonMode = .whileEditing 不应该显示清除按钮。

当然,由于子类化的需要,覆盖deleteBackward()有点不方便。但更好的 UX 值得付出努力!

如果子类化是 no-go,例如当使用 UISearchBar 及其嵌入的 UITextField 时,方法调配也应该没问题。

Swift 4

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

            let  char = string.cString(using: String.Encoding.utf8)!
            let isBackSpace = strcmp(char, "\b")

            if isBackSpace == -92 {
                print("Backspace was pressed")
                return false
            }
}

Swift 5.3

在某些版本中它发生了变化,现在它说:

When the user deletes one or more characters, the replacement string is empty.

所以回答这个:

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
  if string.isEmpty {
    // do something
  }
  return true
}

如果要检测某些字符会被删除

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
  if range.length > 0 {
    // We convert string to NSString instead of NSRange to Range<Int>
    // because NSRange and NSString not counts emoji as one character
    let replacedCharacters = (string as NSString).substring(with: range)
  }
  return true
}

如果您想在空文本字段上检测退格键

class TextField: UITextField {
  var backspaceCalled: (()->())?
  override func deleteBackward() {
    super.deleteBackward()
    backspaceCalled?()
  }
}

旧答案

请不要丢弃您的代码。只需将此扩展放在代码中的某个位置即可。

extension String {
  var isBackspace: Bool {
    let char = self.cString(using: String.Encoding.utf8)!
    return strcmp(char, "\b") == -92
  }
}

然后在你的函数中使用它

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
  if string.isBackspace {
    // do something
  }
  return true
}

Swift 4

我发现使用 strcmp 的比较无关紧要。我们甚至不知道 strcmp 在 hoods.In 后面是如何运作的,所有其他答案在比较当前 char 和 \b 结果时 -8 在 objective-C 和 -92 在 Swift 中。我写这个答案是因为上述解决方案对我不起作用。 ( Xcode 版本 9.3 (9E145) 使用 Swift 4.1 )

仅供参考: 您实际键入的每个字符都是 1utf8 Encoding 中更多元素的数组。 backSpace 字符是 [0]。你可以试试这个。

PS : 不要忘记将正确的 delegates 分配给您的 textFields.

public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

    let  char = string.cString(using: String.Encoding.utf8)!
    if (char.elementsEqual([0])) {
        print("Backspace was pressed")
    }
    else {
        print("WHAT DOES THE FOX SAY ?\n")
        print(char)
    }
    return true
}

Swift 4

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

    //MARK:- If Delete button click
    let  char = string.cString(using: String.Encoding.utf8)!
    let isBackSpace = strcmp(char, "\b")

    if (isBackSpace == -92) {
        print("Backspace was pressed")

        return true
    }
}

我实现了这个功能:

并且在最后一个textFiled为空的情况下,我只想切换到之前的textFiled。我尝试了上面的所有答案,但没有一个适合我的情况。出于某种原因,如果我在 isBackSpace == -92 括号中添加比 print 更多的逻辑,则此方法将停止工作...

至于我,下面的方法更优雅,而且很有魅力:

Swift

class YourTextField: UITextField {

    // MARK: Life cycle

    override func awakeFromNib() {
        super.awakeFromNib()
    }

    // MARK: Methods

    override func deleteBackward() {
        super.deleteBackward()

        print("deleteBackward")
    }

}

感谢@LombaX answer

Swift 5: 根据文本视图委托 documentation,如果 shouldChangeTextIn 方法返回的替换文本为空,则用户按下了退格键按钮。如果范围上限大于范围下限(或范围计数为 1 或更多),则应删除文本。如果范围上限和下限相同(或都等于 0),则没有要删除的文本(长度为 0 的范围将被替换为任何内容)。我测试过,即使在空文本字段上也会调用它。

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
    
    guard text.isEmpty else { return true } // No backspace pressed
    if (range.upperBound > range.lowerBound) {
        print("Backspace pressed")
    } else if (range.upperBound == range.lowerBound) {
        print("Backspace pressed but no text to delete")
        if (textView.text.isEmpty) || (textView.text == nil) {
            print("Text view is empty")
        }
    }
    return true
}