Detect backspace Event in UITextField
Asked Answered
P

13

60

I am searching for solutions on how to capture a backspace event, most Stack Overflow answers are in Objective-C but I need on Swift language.

First I have set delegate for the UITextField and set it to self

self.textField.delegate = self;

Then I know to use shouldChangeCharactersInRange delegate method to detect if a backspace was pressed is all code are in Objective-C. I need in Swift these following method as below is used.

-(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;
}
Poverty answered 8/4, 2015 at 1:25 Comment(2)
And what does your Swift approach to this problem look like? Please. Show us your effort and let us help you where you've got it wrong. Stack Overflow isn't a code writing service.Teerell
Does this answer your question? Detect backspace in empty UITextFieldSurpass
I
90

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
}

Older Swift version

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
}
Interrogative answered 8/4, 2015 at 3:44 Comment(4)
This delegate method is not fired. The delegate is setup in viewDidLoad() method.Vani
-1: this code works, but by accident. The documentation specifies that string will be an empty string if the user deletes one or more characters.China
@gbk Check this link #1978434 to detect backspace even if the TextField is empty.Legislator
This answer is dangerously wrong and should be avoided. The specific return value of strcmp (other than its sign) is meaningless. The string is empty, not \b.Spiller
F
47

I prefer subclassing UITextField and overriding deleteBackward() because that is much more reliable than the hack of using 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()
    }
}

The shouldChangeCharactersInRange hack combined with an invisible character that is placed in the text field has several disadvantages:

  • with a keyboard attached, one can place the cursor before the invisible character and the backspace isn't detected anymore,
  • the user can even select that invisible character (using Shift Arrow on a keyboard or even by tapping on the caret) and will be confused about that weird character,
  • the autocomplete bar offers weird choices as long as there's only this invisible character,
  • Asian language keyboards that have candidate options based on the text field's text will be confused,
  • the placeholder isn't shown anymore,
  • the clear button is displayed even when it shouldn't for clearButtonMode = .whileEditing.

Of course, overriding deleteBackward() is a bit inconvenient due to the need of subclassing. But the better UX makes it worth the effort!

And if subclassing is a no-go, e.g. when using UISearchBar with its embedded UITextField, method swizzling should be fine, too.

Funderburk answered 22/2, 2018 at 10:28 Comment(5)
Thank you! So many redundant answers that do not answer the question yet your correct answer is down here :)Karyn
There are other issues that can arise from the invisible character hack when using asian language keyboards that have candidate options based on the text field's text. This answer should be the accepted answer.Detruncate
Thanks, @Helam, added this aspect to the answer.Funderburk
@OrtwinGentz is there an option for UISearchBar ?Thankless
@BogdanBogdanov as mentioned in my answer, for UISearchBar you could swizzle deleteBackward() of the embedded UISearchTextField (searchTextField property).Funderburk
B
36

Swift 5.3

In some version its changed and now it says:

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

So answer for this:

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

If you want to detect that some characters will be deleted

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
}

If you want detect backspaces even on empty textField

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

Old answer

Please don't trash your code. Just put this extension somewhere in your code.

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

And then just use it in your functions

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
  if string.isBackspace {
    // do something
  }
  return true
}
Blackheart answered 4/4, 2018 at 13:15 Comment(5)
How to detect backspace key when my textfield is empty ?Melcher
This should work: https://mcmap.net/q/162091/-detect-backspace-event-in-uitextfieldBlackheart
@sig Just checked that on iOS 14.2 and it still works. gist.github.com/v57/5f3961abe62bc564a20be4fe9782c70bBlackheart
@DmitryKozlov you're right, removing my comment to not confuse othersCollectanea
Many thanks for deleteBackward. was very helpful when text is emptySynchroflash
B
18

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

:)

Babs answered 27/10, 2016 at 14:32 Comment(0)
K
13

Try this

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

if(string == "") {

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

Note: You can return "true" if you want to allow backspace. Else you can return "false".

Kittrell answered 12/2, 2016 at 7:55 Comment(4)
What happen if current text is the textfield is empty?Agonistic
This delegate wont get calledKittrell
string == "" instead better use .isEmptyCarlitacarlo
This is what I was looking for, thank you! Also +1 to .isEmpty. This seems to be a more straight forward solution than the accepted answer.Acupuncture
C
12

If u need detect backspace even in empty textField (for example in case if u need auto switch back to prev textField on backSpace pressing), u can use combination of proposed methods - add invisible sign and use standard delegate method textField:shouldChangeCharactersInRange:replacementString: like follow

  1. Create invisible sign

    private struct Constants {
        static let InvisibleSign = "\u{200B}"
    }
    
  2. Set delegate for textField

    textField.delegate = self
    
  3. On event EditingChanged check text and if needed add invisible symbol like follow:

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

enter image description here

  1. Add implementation of delegate method 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
        }
    }
    
Carlitacarlo answered 14/9, 2016 at 6:25 Comment(0)
D
7

I implemented this feature:

enter image description here

And in the case where the last textFiled is empty, I just want to switch to the previous textFiled. I tried all of the answers above, but no one works fine in my situation. For some reason, if I add more logic than print in isBackSpace == -92 parentheses block this method just stopped work...

As for me the method below more elegant and works like a charm:

Swift

class YourTextField: UITextField {

    // MARK: Life cycle

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

    // MARK: Methods

    override func deleteBackward() {
        super.deleteBackward()

        print("deleteBackward")
    }

}

Thanks @LombaX for the answer

Downing answered 30/8, 2018 at 15:45 Comment(1)
How do you access instance element of Controller Class in deleteBackward methodFloury
P
6

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
            }
}
Pablopabon answered 15/3, 2018 at 8:46 Comment(1)
That's not a good solution, please look here #51578066Shonda
S
5

Swift 4

I find the comparison using strcmp irrelevant. We don't even know how strcmp is operating behind the hoods.In all the other answers when comparing current char and \b results are -8 in objective-C and -92 in Swift. I wrote this answer because the above solutions did not work for me. ( Xcode Version 9.3 (9E145) using Swift 4.1 )

FYI : Every character that you actually type is an array of 1 or more elements in utf8 Encoding. backSpace Character is [0]. You can try this out.

PS : Don't forget to assign the proper delegates to your 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
}
Subscription answered 21/5, 2018 at 13:54 Comment(0)
A
2

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
    }
}
Alopecia answered 26/5, 2018 at 10:43 Comment(0)
T
1

Swift 4: If the user presses the backspace button, string is empty so this approach forces textField to only accept characters from a specified character set (in this case utf8 characters) and backspaces (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
    }
}
Tilburg answered 16/8, 2017 at 21:44 Comment(0)
C
1

Swift 5: Per the text view delegate documentation, if the replacement text returned by the shouldChangeTextIn method is empty, the user pressed the backspace button. If the range upper bound is greater than the range lower bound (or range count is 1 or more), text should be deleted. If the range upper and lower bounds are the same (or both equal 0), then there is no text to delete (a range of length 0 to be replaced by nothing). I tested, and this will get called even on an empty textfield.

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
}
Carpo answered 7/2, 2021 at 20:30 Comment(1)
Please edit and include the link to the source of the quote.Fetishist
B
0

I came here looking for an answer of how to detect deletions. I wanted to know when the UITextView was empty after a user taps delete. As I was testing, I realized I needed to capture whole word deletions and cuts as well. Here's the answer I came up with.

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

Thanks for all the previous helpful answers. I hope this helps someone.

Barrel answered 9/9, 2022 at 22:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.