Search code examples
iosobjective-cuitextfieldbackspace

VENTokenField hold backspace key to delete tokens


While trying to allow multi token deletions, as user holds the backspace key in VENTokenField to act the same as the native email app, or messages app, I have come across many problems...

First, I can only detect one tap on the backspace key from the initial code the VENToken's UITextField subclass offer (which is technically touching private API) - (BOOL)keyboardInputShouldDelete:(UITextField *)textField. That is fine, but not helpful for detecting long press on backspace button, which only works while you actually have characters in a certain UITextField, and not while the UITextField is empty such as in our case.

I have also came across this blogpost which suggest another approach of accessing more of the private API, however, does not offer solution to my problem. As it's not documented, I was wondering if there is a valid way to detect this event at all?


Solution

  • I've resolved it by first, comment out anything that was in VENBackspaceTextField class'

    keyboardInputShouldDelete:(UITextField *)textField 
    

    Then, added 2 consts in VENTokenField header:

    NSString * const kTextEmpty = @"\u200B"; // Zero-Width Space
    NSString * const kTextHidden = @"\u200D"; // Zero-Width Joiner
    

    Everytime the token becomes first responder, make sure the textField has the empty text:

    - (void)inputTextFieldBecomeFirstResponder {    
       [self.inputTextField becomeFirstResponder];
       if (self.tokens.count) {
          [self.inputTextField setText:kTextEmpty];
       }
       ...
    }
    

    And set it to hidden when cursor is not visible:

    - (void)setCursorVisibility {
       NSArray *highlightedTokens = [self.tokens filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(VENToken *evaluatedObject, NSDictionary *bindings) {
        return evaluatedObject.highlighted;
       }]];
       BOOL visible = [highlightedTokens count] == 0;
       if (visible) {
          [self inputTextFieldBecomeFirstResponder];
       } else {
          [self.invisibleTextField becomeFirstResponder];
          [self.invisibleTextField setText:kTextHidden];
       }
    }
    

    Then, modified the textField Delegate method:

    - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    if (self.tokens.count && [string isEqualToString:@""] && [textField.text isEqualToString:kTextEmpty]){
        VENToken *lastToken = [self.tokens lastObject];
        lastToken.highlighted = YES;
        [_inputTextField setText:kTextHidden];
        _inputTextField.alpha = 0.0;
        return NO;
    }
    
    if ([textField.text isEqualToString:kTextHidden]){
        [self deleteHighlighted];
        [self unhighlightAllTokens];
    
        return (![string isEqualToString:@""]);
    }
    
    //If there are any highlighted tokens, delete
    [self deleteHighlighted];
    return YES;
    }