10

I'm using UITextField because I want to have a custom pop-up keyboard. However, I don't want the user to be able to change the insertion point or have access to the copy, paste menu.

I have found two useful stackoverflow questions, and attempted to implement them:

I have removed the menu by subclassing the UITextField and implementing the method:

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
    return NO;
}

However, I have failed to stop the field from being selected when the user double taps it:

I have tried removing the gestureRecognizers which I believe to be responsible for the selection behaviour, but with no success. So what am I doing wrong?

@property (nonatomic, strong) MinimalTextField *inputText;
...
@synthesize inputText;
...

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear: animated];    
    NSLog(@"%ld gestureRecognizers initially ", (long)inputText.gestureRecognizers.count);

    for (UIGestureRecognizer *gestureRecognizer in inputText.gestureRecognizers) {        
        if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
            UITapGestureRecognizer *tapGestureRecognizer = (UITapGestureRecognizer *)gestureRecognizer;
            if ([tapGestureRecognizer numberOfTapsRequired] == 2) {
                NSLog(@"found & removed: %@", tapGestureRecognizer);
                [inputText removeGestureRecognizer:tapGestureRecognizer];
            }
        }
        if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) {
            UILongPressGestureRecognizer *longPressGestureRecognizer = (UILongPressGestureRecognizer *)gestureRecognizer;
            NSLog(@"found & removed: %@", longPressGestureRecognizer);
            [inputText removeGestureRecognizer:longPressGestureRecognizer];
        }
    }

    NSLog(@"%ld gestureRecognizers remaining", (long)inputText.gestureRecognizers.count);

    for (UIGestureRecognizer *gestureRecognizer in inputText.gestureRecognizers) {
        NSLog(@"gestureRecognizer: %@", gestureRecognizer);
    }
}

This code produces the following output, so I know it is working, but it fails to affect the double tap action.

7 gestureRecognizers initially 
found & removed: <UITextTapRecognizer: 0x7ff6086571f0; state = Possible; delaysTouchesEnded = NO; view = <MinimalTextField 0x7ff608414b10>; target= <(action=oneFingerDoubleTap:, target=<UITextInteractionAssistant 0x7ff608652de0>)>; numberOfTapsRequired = 2>
found & removed: <UILongPressGestureRecognizer: 0x7ff608658180; state = Possible; delaysTouchesEnded = NO; view = <MinimalTextField 0x7ff608414b10>; target= <(action=twoFingerRangedSelectGesture:, target=<UITextInteractionAssistant 0x7ff608652de0>)>>
found & removed: <UIVariableDelayLoupeGesture: 0x7ff608658a40; state = Possible; delaysTouchesEnded = NO; view = <MinimalTextField 0x7ff608414b10>; target= <(action=loupeGesture:, target=<UITextInteractionAssistant 0x7ff608652de0>)>>

4 gestureRecognizers remaining
gestureRecognizer: <UITextTapRecognizer: 0x7ff608653960; state = Possible; delaysTouchesEnded = NO; view = <MinimalTextField 0x7ff608414b10>; target= <(action=oneFingerTripleTap:, target=<UITextInteractionAssistant 0x7ff608652de0>)>; numberOfTapsRequired = 3>
gestureRecognizer: <UITextTapRecognizer: 0x7ff6086576e0; state = Possible; delaysTouchesEnded = NO; view = <MinimalTextField 0x7ff608414b10>; target= <(action=twoFingerSingleTap:, target=<UITextInteractionAssistant 0x7ff608652de0>)>; numberOfTouchesRequired = 2>
gestureRecognizer: <UITapAndAHalfRecognizer: 0x7ff608657c70; state = Possible; view = <MinimalTextField 0x7ff608414b10>; target= <(action=tapAndAHalf:, target=<UITextInteractionAssistant 0x7ff608652de0>)>>
gestureRecognizer: <UITextTapRecognizer: 0x7ff6086585f0; state = Possible; delaysTouchesEnded = NO; view = <MinimalTextField 0x7ff608414b10>; target= <(action=oneFingerTap:, target=<UITextInteractionAssistant 0x7ff608652de0>)>>

I have even tried adding the following code to my subclass of UITextField:

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    NSLog(@"Gesture should begin");
    if ([gestureRecognizer isKindOfClass:[UIRotationGestureRecognizer class]])
        NSLog(@"rotate");
    if ([gestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]])
        NSLog(@"pinch");
    if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
        NSLog(@"tap");
        NSLog(@"numberOfTouches: %ld", (long)gestureRecognizer.numberOfTouches);
    }
    if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]])
        NSLog(@"pan");
    if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]])
        NSLog(@"long");
    if ([gestureRecognizer isKindOfClass:[UISwipeGestureRecognizer class]])
        NSLog(@"swipe");
    return YES;
}

However, there is no property gestureRecognizer.numberOfTaps available, so how can I tell how often it has been tapped.

Community
  • 1
  • 1
Steve
  • 173
  • 2
  • 12

2 Answers2

9

You need to add the following overrides to your UITextField subclass (they basically disable the UIMenuController and hide the selection and caret rects):

Objective-C

- (CGRect)caretRectForPosition:(UITextPosition*) position {
    return CGRectNull;
}

- (NSArray *)selectionRectsForRange:(UITextRange *)range {
    return nil;
}

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
    return NO;
}

Swift

override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
    false
}

override func selectionRects(for range: UITextRange) -> [UITextSelectionRect] {
    []
}

override func caretRect(for position: UITextPosition) -> CGRect {
    .null
}
alobaili
  • 761
  • 9
  • 23
  • 1
    the only proper solution I found, good job. I still see a little glitch when you touch the textfield a second time, the selection on 0 chararacter at the start appears during some frames.. Still the best solution tho. – itMaxence Nov 23 '21 at 15:10
  • 1
    @itMaxence this seems to have fixed that bug for me: set `caretRect` to `.null` instead of `.zero` – Michael Ellis Jan 22 '22 at 14:40
  • 1
    @MichaelEllis Thanks for the suggestion. I approved it. I didn't know `.null` existed! :D I assume the same can be said for the Objective-C version by replacing `CGRectZero` with `CGRectNull`. I haven't tested it. – alobaili Jan 23 '22 at 07:49
  • When I return `.null` from `caretRect(for position: UITextPosition)`, I have this in Xcode log : `_NSLayoutTreeLineFragmentRectForGlyphAtIndex invalid glyph index 9223372036854775807`. I don't know if that's a big deal ? ‍♂️ – bob Jan 27 '22 at 10:34
  • @bob it sounds like a nil reference error warning message log output. LayoutTree -LineFragmentRect - ForGlyph AtIndex, there's a nil caret position for the line fragment rect in the layout tree. Probably nothing to worry about, I haven't had any issues nor have my testers and users for iOS 15.3 and xcode 13.2.1 – Michael Ellis Mar 16 '22 at 00:41
-1

UITextView has an attribute selectable that may help you

Leonardo
  • 1,740
  • 18
  • 27
  • 1
    That's correct, I may end up using UITextView. Unfortunately I wrote the wrong title to this question, I have just amended it. – Steve Sep 29 '15 at 17:17
  • Let me re-phrase my response. I still have the same problem, I don't know how to disable the selection of a UITextField. In the original question when I first posted it I mistakenly wrote UITextView instead of UITextField, I edited it to correct this mistake. I need to use UITextField in my code and unfortunately it does NOT have an attribute selectable. – Steve Oct 14 '15 at 10:15
  • Not sure how to do that, but what about if you disable all clicks on UITextField by placing a button over it that calls `[textField becomeFirstResponder];`on it's action method – Leonardo Oct 14 '15 at 11:58