40

I have subclassed UITextField and implemented the UIKeyInput protocol's deleteBackward method to detect backspace being pressed. This works fine on iOS 7 but not on iOS 8.

deleteBackward is not called on the UITextField anymore when I press the backspace key.

I've checked the documentation and the release notes and nothing points to the reason why this could happen. Any pointers?

VNVN
  • 501
  • 1
  • 4
  • 7
  • Keep an eye on [this question](http://stackoverflow.com/questions/25354467/detect-backspace-in-uitextfield-in-ios8) - seems to be asking about the same thing. – thegrinner Aug 18 '14 at 20:10

10 Answers10

47

A lot of people have been saying this is a bug, but being that this problem still exists in the GM I'm starting to think it might be a change in logic. With that said, I wrote this bit of code for my app and have tested it on iOS 7-8.

Add the following method to your UITextField subclass.

- (BOOL)keyboardInputShouldDelete:(UITextField *)textField {
    BOOL shouldDelete = YES;

    if ([UITextField instancesRespondToSelector:_cmd]) {
        BOOL (*keyboardInputShouldDelete)(id, SEL, UITextField *) = (BOOL (*)(id, SEL, UITextField *))[UITextField instanceMethodForSelector:_cmd];

        if (keyboardInputShouldDelete) {
            shouldDelete = keyboardInputShouldDelete(self, _cmd, textField);
        }
    }

    BOOL isIos8 = ([[[UIDevice currentDevice] systemVersion] intValue] == 8);
    BOOL isLessThanIos8_3 = ([[[UIDevice currentDevice] systemVersion] floatValue] < 8.3f);

    if (![textField.text length] && isIos8 && isLessThanIos8_3) {
        [self deleteBackward];
    }

    return shouldDelete;
}

This code is slightly before the red line of private API's, however you should have no problem using it. My app with this code is in the app store.

To explain a little, were calling the super implementation of this method to avoid losing code. After were going to call -deleteBackward if there is no text and the iOS version is between 8-8.2.

EDIT: 1/22/15

It also might be helpful to subclass the -deleteBackward method of your subclassed UITextField. This fixes a few conditional bugs. One being if you use a custom keyboard. Heres an example of the method.

- (void)deleteBackward {
    BOOL shouldDismiss = [self.text length] == 0;

    [super deleteBackward];

    if (shouldDismiss) {
        if ([self.delegate respondsToSelector:@selector(textField:shouldChangeCharactersInRange:replacementString:)]) {
            [self.delegate textField:self shouldChangeCharactersInRange:NSMakeRange(0, 0) replacementString:@""];
        }
    }
}

EDIT: 4/13/15

As @Gee.E commented, iOS 8.3 has fixed this issue. The code has been updated to reflect the changes.

cnotethegr8
  • 7,342
  • 8
  • 68
  • 104
  • 4
    This one should be accepted as the correct answer and works perfectly as a workaround while the ios8 bug ( https://devforums.apple.com/message/1009150#1009150 ) is still around. – pIkEL Sep 28 '14 at 15:02
  • 1
    Yes, this works for iOS 8, which changed this behavior (bug or not). Also works for UITextView. Thanks! – Stian Høiland Oct 22 '14 at 07:46
  • @StianHøiland suggested in an edit to change the last conditions logic from `![textField.text length]` to `(![textField.text length] || shouldDelete)`. This is not needed because in iOS8 `-keyboardInputShouldDelete:` has been removed, so the value of `shouldDelete` will always equal `YES`. Also if for some reason this method were to reappear in the future, `-deleteBackward` would be called twice, once natively and once from this code. – cnotethegr8 Oct 22 '14 at 08:23
  • @cnotethegr8 this works with iOS8 default keboard but doesn't with custom keyboards such as SwiftKey. Do you know any method to achieve the same with custom keyboards? – Michał Miszczyszyn Jan 21 '15 at 08:49
  • @Miszy the code above should only be used to extend the deleting functionality. All of your code that manages what happens once the text has been deleted needs to be in the delegate callbacks. If you're using `-textField:shouldChangeCharactersInRange:replacementString:` you can check if the empty string was deleted like this, `if ([string isEqualToString:@""] && NSEqualRanges(range, NSMakeRange(0, 0))) { }`. – cnotethegr8 Jan 21 '15 at 13:22
  • @cnotethegr8 but this method won't be even called if the input field is empty. What I want to achieve is detect the "delete" key even if the input has 0 characters inside. – Michał Miszczyszyn Jan 21 '15 at 14:22
  • 2
    @Miszy ahh yes, you have to subclass `-deleteBackward`. Ill update the answer with what I use in my app. – cnotethegr8 Jan 22 '15 at 09:30
  • 1
    And now it just works. Magic. Thank you for your time @cnotethegr8! :) – Michał Miszczyszyn Jan 22 '15 at 20:19
  • `![textField.text length]` shouldn't be changed to [textField.text length]? – Allen Apr 07 '15 at 15:43
  • And I removed `[super deleteBackward]`, because `deleteBackward` would be called twice. I can't find any documentation that subclass `deleteBackward` must call `[super deleteBackward]`. – Allen Apr 07 '15 at 15:45
  • @Allen `-deleteBackward` is a method called only when the `textField` has no text and the user still tries to delete. So if you wish to keep to the native functionality then use `![textField.text length]`. You can remove `[super deletebackward]` if you choose but I have learnt not calling super doesn't pay for the one line you save. – cnotethegr8 Apr 08 '15 at 03:25
  • @cnotethegr8 From Apple document of deletebackward: `Remove the character just before the cursor from your class’s backing store and redisplay the text.` It seems that `deletebackward` is called whether the textField has text or has no text. – Allen Apr 08 '15 at 03:58
  • @Allen ahh yes, you're correct. (It's been awhile since my mind was around this code.) The reason why the code says `![textField.text length]` is because iOS calls `-deleteBackward` on its own. So we're only going to call this method when iOS doesn't call it. Which is when there is no text and the user is still deleting and we're running iOS8. – cnotethegr8 Apr 08 '15 at 04:13
  • 5
    It seems this bug was addressed in iOS 8.3, since the `-deleteBackward` call gets through now (without the code from this answer) – Gerald Eersteling Apr 09 '15 at 08:35
  • **keyboardInputShouldDelete** is not getting called in iOS 8.1 – Durai Amuthan.H Apr 24 '17 at 15:33
23

You can detect when user deletes text by using backspace by implementing UITextField delegate method:

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    if (range.length==1 && string.length==0)
        NSLog(@"backspace tapped");

    return YES;
}
almas
  • 7,090
  • 7
  • 33
  • 49
  • Doesn't that get called each time you press a key. I think you need to check range.location == 0 && range.length == 1 && string.length == 0 – VNVN Aug 18 '14 at 20:33
  • @VNVN, you are correct that string.length should be equal to 0, I edited my answer. However, I'm not sure why range.location should be 0? It is different every time... – almas Aug 18 '14 at 21:26
  • You are right. range.location is not required. That was specific to my use case, sorry! – VNVN Aug 18 '14 at 21:30
  • 14
    this won't detect a backspace if the text field's text is empty. deleteBackward was the way to detect a backspace in all cases, but is not getting called any more on iOS 8.. any idea on how to detect in the case of an empty text field? – Pablo Zbigy Jablonski Aug 21 '14 at 21:36
  • It works, putting out the Swift 2.0 version of it. //For Detecting Backspace based Deletion of Entire Word in TextField func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { if (range.length == 1 && string.isEmpty){ print("Used Backspace") } return true } – Abhijeet Oct 06 '15 at 06:07
  • This condition fails when the user presses backspace while the cursor is in the start position (there's nothing to backspace) – Nofel Mahmood Oct 20 '15 at 08:52
12

You must look an example for MBContactPicker on github. Deletion of contacts at MBContactPicker via Backspace button on iOS8 tested by me. And it works greatly! You can use its as example.

Author of MBContactPicker use next method: When UITextField must become empty (or before call becomeFirstResponder when it is empty), he save single whitespace symbol there. And then when you press Backspace button (when focus was set to end of text of your UITextField), method

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string

will work. Inside it you must use check like this:

NSString *resultString = [textField.text stringByReplacingCharactersInRange:range withString:string];
BOOL isPressedBackspaceAfterSingleSpaceSymbol = [string isEqualToString:@""] && [resultString isEqualToString:@""] && range.location == 0 && range.length == 1;
if (isPressedBackspaceAfterSingleSpaceSymbol) {
    //  your actions for deleteBackward actions
}

So, you must always control that UITextField contains single whitespace.

This is not hack. So, user willn't noticed about some behaviour was changed

Lal Krishna
  • 15,485
  • 6
  • 64
  • 84
iVader
  • 212
  • 2
  • 4
  • 9
    This is the definition of a hack. But that doesn't mean hacks don't work. – Awesome-o Sep 26 '14 at 17:38
  • 1
    In my definition, the hack is not native realization. It most probably prohibited realization, which may become a reason of rejection of your build from the AppStore side – iVader Apr 09 '15 at 10:56
  • BOOL isPressedBackspaceAfterSingleSpaceSymbol = [resultString isEqualToString:@""]; will work too – mskw Jan 04 '17 at 21:18
7

Swift 2.2:

func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {

    if text == "" {
      print("Backspace has been pressed")
    }

    return true
}
Włodzimierz Woźniak
  • 3,106
  • 1
  • 26
  • 23
6

In iOS8, some custom keyboards delete whole word, so only check string.length is OK.

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    if (string.length==0) { //Delete any cases
       if(range.length > 1){
          //Delete whole word
       }
       else if(range.length == 1){
          //Delete single letter
       }
       else if(range.length == 0){
          //Tap delete key when textField empty
       }  
    }  
    return YES;
}
LE SANG
  • 10,955
  • 7
  • 59
  • 78
2

This does not explicitly answer the original question but worth nothing that in the documentation for textField(_:shouldChangeCharactersIn:replacementString:), it says:

"string: The replacement string for the specified range. During typing, this parameter normally contains only the single new character that was typed, but it may contain more characters if the user is pasting text. When the user deletes one or more characters, the replacement string is empty."

Thus, we can detect backspaces in a UITextFieldDelegate if we implement textField(_:shouldChangeCharactersIn:replacementString:) and check if the length of string is 0.

A lot of other answers here have used this same logic without referencing the documentation so hopefully getting it right from the source makes people more comfortable using it.

Brian Sachetta
  • 3,319
  • 2
  • 34
  • 45
1

Swift 2.0 version for Detecting BackSpace based deletion, referencing code post from almas

//For Detecting Backspace based Deletion of Entire Word in TextField
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { 
    if (range.length == 1 && string.isEmpty){
        print("Used Backspace")
    }
return true
}
Community
  • 1
  • 1
Abhijeet
  • 8,561
  • 5
  • 70
  • 76
  • 2
    Testing if `string.isEmpty` is the correct way to test for deletion, but `range.length == 1` is wrong. If the user has a bunch of text selected and hits delete the range length will be larger than 1 as they're deleting multiple characters. – par Mar 14 '17 at 01:12
0
- (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;

}

Basically this method detects which button you are pressing (or have just pressed). This input comes in as an NSString. We convert this NSString to a C char type and then compare it to the traditional backspace character (\b). Then if this strcmp is equal to -8, we can detect it as a backspace.

mars
  • 1,651
  • 2
  • 15
  • 20
0

swift 2:

if (string.characters.count == 0 && range.length == 1) {
            return true
}

you should use like this string.characters.count

fatihyildizhan
  • 8,614
  • 7
  • 64
  • 88
0

func keyboardInputShouldDelete(_ textField: UITextField) -> Bool { }

This function is called when you hit delete key

Ankit Soni
  • 129
  • 1
  • 2