How to detect delete key on an UITextField in iOS 8?
Asked Answered
Z

10

41

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?

Zibet answered 18/8, 2014 at 20:8 Comment(1)
Keep an eye on this question - seems to be asking about the same thing.Oedipus
F
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

Farrison answered 12/9, 2014 at 6:10 Comment(3)
This is the definition of a hack. But that doesn't mean hacks don't work.Yeseniayeshiva
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 sideFarrison
BOOL isPressedBackspaceAfterSingleSpaceSymbol = [resultString isEqualToString:@""]; will work tooBradlybradman
R
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.

Recourse answered 16/9, 2014 at 7:24 Comment(15)
This one should be accepted as the correct answer and works perfectly as a workaround while the ios8 bug ( devforums.apple.com/message/1009150#1009150 ) is still around.Fredericksburg
Yes, this works for iOS 8, which changed this behavior (bug or not). Also works for UITextView. Thanks!Conferva
@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.Recourse
@Recourse 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?Unbated
@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))) { }.Recourse
@Recourse 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.Unbated
@Miszy ahh yes, you have to subclass -deleteBackward. Ill update the answer with what I use in my app.Recourse
And now it just works. Magic. Thank you for your time @cnotethegr8! :)Unbated
![textField.text length] shouldn't be changed to [textField.text length]?Dead
And I removed [super deleteBackward], because deleteBackward would be called twice. I can't find any documentation that subclass deleteBackward must call [super deleteBackward].Dead
@Dead -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.Recourse
@Recourse 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.Dead
@Dead 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.Recourse
It seems this bug was addressed in iOS 8.3, since the -deleteBackward call gets through now (without the code from this answer)Jenelljenelle
keyboardInputShouldDelete is not getting called in iOS 8.1Rugen
R
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;
}
Rebus answered 18/8, 2014 at 20:17 Comment(6)
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 == 0Zibet
@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...Rebus
You are right. range.location is not required. That was specific to my use case, sorry!Zibet
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?Acquiescent
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 }Waziristan
This condition fails when the user presses backspace while the cursor is in the start position (there's nothing to backspace)Chole
F
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

Farrison answered 12/9, 2014 at 6:10 Comment(3)
This is the definition of a hack. But that doesn't mean hacks don't work.Yeseniayeshiva
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 sideFarrison
BOOL isPressedBackspaceAfterSingleSpaceSymbol = [resultString isEqualToString:@""]; will work tooBradlybradman
P
7

Swift 2.2:

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

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

    return true
}
Perilous answered 18/4, 2016 at 12:40 Comment(0)
S
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;
}
Strobile answered 9/7, 2015 at 3:46 Comment(0)
E
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.

Egyptology answered 27/7, 2018 at 12:54 Comment(0)
W
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
}
Waziristan answered 6/10, 2015 at 6:3 Comment(1)
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.Dewar
M
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.

Mishear answered 16/6, 2015 at 12:16 Comment(0)
Z
0

swift 2:

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

you should use like this string.characters.count

Zoes answered 18/11, 2015 at 14:17 Comment(0)
F
0

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

This function is called when you hit delete key

Fai answered 8/1, 2020 at 8:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.