Обнаружить Backspace Событие в UITextField

Я ищу решения о том, как захватить событие возврата, большинство ответов Stack Overflow написано на Objective-C, но мне нужно на языке Swift.

Сначала я установил делегат для UITextField и установил его на себя

self.textField.delegate = self;

Тогда я знаю, чтобы использовать shouldChangeCharactersInRange метод делегата, чтобы определить, был ли нажат backspace, если весь код находится в 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;
}

13 ответов

Решение

Надеюсь поможет тебе:p

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
}

Я предпочитаю подкласс UITextField и переопределение deleteBackward() потому что это гораздо надежнее, чем использование shouldChangeCharactersInRange:

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 Взлом в сочетании с невидимым символом, помещаемым в текстовое поле, имеет несколько недостатков:

  • при подключенной клавиатуре можно поместить курсор перед невидимым символом, и возврат больше не обнаруживается,
  • пользователь может даже выбрать этот невидимый символ (используя Shift Arrow на клавиатуре или даже, нажав на каретку), и будет смущен этим странным персонажем,
  • панель автозаполнения предлагает странный выбор, пока есть только этот невидимый персонаж,
  • placeholder больше не показывается,
  • кнопка очистки отображается, даже если она не должна clearButtonMode = .whileEditing,

Конечно, переопределение deleteBackward() это немного неудобно из-за необходимости создания подклассов. Но лучший UX стоит того!

И если подклассы не нужны, например, при использовании UISearchBar со встроенным UITextFieldМетод swizzling тоже должен быть в порядке.

Пожалуйста, не трогайте свой код. Просто поместите это расширение где-нибудь в своем коде. (Swift 4.1)

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
}

В Свифт 3

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
}

:)

Если вам нужно обнаружить backspace даже в пустом textField (например, если вам нужно автоматически переключиться обратно на prev textField при нажатии backSpace), вы можете использовать комбинацию предложенных методов - добавить невидимый знак и использовать стандартный метод делегата textField:shouldChangeCharactersInRange:replacementString: как следовать

  1. Создать невидимый знак

    private struct Constants {
        static let InvisibleSign = "\u{200B}"
    }
    
  2. Установить делегат для textField

    textField.delegate = self
    
  3. На мероприятии 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
        }
    }
    

Попробуй это

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

if(string == "") {

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

Примечание. Вы можете вернуть "true", если хотите разрешить возврат. В противном случае вы можете вернуть "ложь".

Я реализовал эту функцию:

И в случае, когда последний textFiled пуст, я просто хочу переключиться на предыдущий textFiled. Я попробовал все ответы выше, но никто не работает нормально в моей ситуации. По какой-то причине, если я добавлю больше логики, чем print в isBackSpace == -92 в скобках этот метод просто перестал работать...

Как по мне, метод ниже более элегантный и работает как шарм:

стриж

class YourTextField: UITextField {

    // MARK: Life cycle

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

    // MARK: Methods

    override func deleteBackward() {
        super.deleteBackward()

        print("deleteBackward")
    }

}

Спасибо @LombaX за ответ

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 4

Я нахожу сравнение, используя strcmp не имеет значения. Мы даже не знаем как strcmp работает за капотом. Во всех других ответах при сравнении текущего символа и \b результаты -8 в объективе-C и -92 в Свифте. Я написал этот ответ, потому что вышеупомянутые решения не работали для меня. (Версия XCode 9.3 (9E145) используя Swift 4.1)

К вашему сведению: каждый символ, который вы вводите, является массивом 1 или более элементов в utf8 Encoding, символ возврата [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
    }
}

Swift 5: согласно документации делегата текстового представления , если текст замены, возвращаемый методом 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
}

Swift 4: если пользователь нажимает кнопку возврата, строка становится пустой, поэтому этот подход заставляет textField принимать символы только из указанного набора символов (в данном случае utf8 символов) и символы возврата (в случае string.isEmpty).

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

Я пришел сюда в поисках ответа о том, как обнаружить удаления. Я хотел знать, когда UITextView был пуст после того, как пользователь коснулсяdelete. Во время тестирования я понял, что мне нужно также фиксировать удаление и вырезание целых слов. Вот ответ, который я придумал.

      extension SomeViewController: UITextViewDelegate {
    
    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        
        let deleteTapped = text == ""
        let cursorAtBeginning = range.location == 0
        let deletedCharactersEqualToTextViewCount = range.length == textView.text.count

        let everythingWasDeleted = deleteTapped && cursorAtBeginning && deletedCharactersEqualToTextViewCount
        if everythingWasDeleted {
            // Handle newly empty view from deletion
        } else {
            // Handle other situation
        }
            
        return true
    }
}

Спасибо за все предыдущие полезные ответы. Я надеюсь, что это помогает кому-то.

Другие вопросы по тегам