10

I want to intercept long press on UITextview, but don't want to disable the context menu option at the same time.

If I use gesture recognizer on textview, it will disable context menu so I am using the method like below now.

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {    
    //fire my method here
}

But, it only fires the method when context menu shows up after the user long press some words. So when the user long press a blank space, then only the magnifying glass shows up, I can't fire the method at the time.

Does anyone have better ideas? Thanks!

//////The Problem Solved//////

Thanks to @danh and @Beppe, I made it even with tap gesture on UITextView. I wanted to show the font bar on textview by long press.

@First, I subclassed the UITextview.

@interface LisgoTextView : UITextView {
    BOOL        pressing_;

}

@property (nonatomic)         BOOL      pressing;

@end


@interface LisgoTextView (private)
    - (void)longPress:(UIEvent *)event;
@end


@implementation LisgoTextView

@synthesize pressing = pressing_;


//--------------------------------------------------------------//
#pragma mark -- Long Press Detection --
//--------------------------------------------------------------//

- (void)longPress:(UIEvent *)event {

    if (pressing_) {

        //post notification to show font edit bar
        NSNotification *fontEditBarNotification = [NSNotification notificationWithName:@"fontEditBarNotification" 
                                                                                object:nil userInfo:nil];
        [[NSNotificationCenter defaultCenter] postNotification:fontEditBarNotification];
    }    
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesBegan:touches withEvent:event];
    [self performSelector:@selector(longPress:) withObject:event afterDelay:0.7];
    pressing_ = YES;
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesEnded:touches withEvent:event];    
    pressing_ = NO;
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesMoved:touches withEvent:event];    
    pressing_ = NO;
}

@I used the delay to solve the conflict with tap gesture I implemented on UITextView.

- (void)tapGestureOnTextView:(UITapGestureRecognizer *)sender {

    //cancel here if long press was fired first
    if (cancelTapGesture_) {
        return;
    }

    //don't fire show font bar 
    cancelShowFontBar_ = YES;
    [self performSelector:@selector(enableShowFontBar) withObject:nil afterDelay:1.0];

    //method here   
}


- (void)showFontEditBar {

    //cancel here if tap gesture was fired first
    if (cancelShowFontBar_) {
        return;
    }

    if (fontEditBarExists_ == NO) {

        //method here    

        //don't fire tap gesture
        cancelTapGesture_ = YES;
        [self performSelector:@selector(enableTapGesture) withObject:nil afterDelay:1.0];
    }
}

- (void)enableTapGesture {
    cancelTapGesture_ = NO;
}

- (void)enableShowFontBar {
    cancelShowFontBar_ = NO;
}
Umeumeume
  • 1,952
  • 3
  • 21
  • 40

6 Answers6

8

I usually avoid subclassing unless the docs explicitly suggest, this worked for me. Long press and context menu. Whoops - Answer just loaded by @Beppe. Great minds think alike :-)

@interface TextViewSubclass ()
@property(assign,nonatomic) BOOL pressing;
@end

@implementation TextViewSubclass
@synthesize pressing=_pressing;

- (void)longPress:(UIEvent *)event {
    NSLog(@"long press");
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesBegan:touches withEvent:event];
    self.pressing = YES;
    [self performSelector:@selector(longPress:) withObject:event afterDelay:2.0];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesEnded:touches withEvent:event];
    [NSObject cancelPreviousPerformRequestsWithTarget:self];
    self.pressing = NO;
}
@end
danh
  • 62,181
  • 10
  • 95
  • 136
  • Even though I'm still struggling to solve the conflict with tapGesture I implemented on UITextview, this is the answer to my question. Thank you very much. – Umeumeume Mar 20 '12 at 16:40
  • I made it with the conflict so wrote the work flaw on the answer part. – Umeumeume Mar 21 '12 at 07:27
4

This worked for me. I just wanted to control what happens when the user taps or long presses a link

- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange
{
    // check for long press event
    BOOL isLongPress = YES;
    for (UIGestureRecognizer *recognizer in self.commentTextView.gestureRecognizers) {
        if ([recognizer isKindOfClass:[UILongPressGestureRecognizer class]]){
            if (recognizer.state == UIGestureRecognizerStateFailed) {
                isLongPress = NO;
            }
        }
    }

    if (isLongPress) {
        // user long pressed a link, do something
    } else {
        // user tapped on a link, do something
    }

    // return NO cause you dont want the normal behavior to occur
    return NO;
}
Lucas Chwe
  • 2,578
  • 27
  • 17
  • This is failing for me in iOS 9. When NSLogging the recognizers, there a new mysterious recognizer in `Failed` state. I switched the logic around to look for at least one long-press recognizer in `Began` state. Still kind of hackish, but working in iOS 8 and 9 now. See post below with new code (posting separately to be able to format the code). – Christian Garbin Sep 24 '15 at 19:53
  • I think the reason why this is not working is `UIPreviewGestureRecognizer`which is subclass of `UILongPressGestureRecognizer` and that one fails. So I think instead of `isKindOfClass` just use `isMemberOfClass` to not to check subclasses. – josip04 Jun 13 '17 at 09:39
3

Adaptation of Lucas Chwe's answer to work on iOS 9 (and 8). See comment in his answer.

The gist: invert the logic by starting with "no long press", then switch to "long press detected" if at least one UILongPressGestureRecognizer is in Began state.

BOOL isLongPress = NO;
for (UIGestureRecognizer *recognizer in textView.gestureRecognizers) {
    if ([recognizer isKindOfClass:[UILongPressGestureRecognizer class]]) {
        if (recognizer.state == UIGestureRecognizerStateBegan) {
            isLongPress = YES;
        }
    }
}

Still a bit hackish, but working for iOS 8 and 9 now.

Christian Garbin
  • 2,512
  • 1
  • 23
  • 31
2

This is a little tricky, but it works for me.

I add a subclass of UIButton on top of my UITextView, check for long touches and pass them to UITextView this way:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesBegan:touches withEvent:event];
    isLongTouchDetected = NO;
    [self performSelector:@selector(longTouchDetected) withObject:nil afterDelay:1.0];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    if (isLongTouchDetected == YES) {
        [super touchesCancelled:touches withEvent:event];
    } else {
        [super touchesEnded:touches withEvent:event];
        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(longTouchDetected) object:nil]; 
    }
}

- (void)longTouchDetected {
    isLongTouchDetected = YES;
    // pass long touch to UITextView
}
Giuseppe Garassino
  • 2,272
  • 1
  • 27
  • 47
  • I haven't tried this way, since I was subclassing UITextview. But, this must be working as well. Thanks. – Umeumeume Mar 20 '12 at 16:42
1

For SWIFT [Easiest Way]

extension UITextField {

    override public func canPerformAction(action: Selector, withSender sender: AnyObject?) -> Bool {
        if action == "paste:" {
            return false
        }

        return super.canPerformAction(action, withSender: sender)
    }

    override public func addGestureRecognizer(gestureRecognizer: UIGestureRecognizer) {
        if gestureRecognizer.isKindOfClass(UILongPressGestureRecognizer) {
            gestureRecognizer.enabled = false
        }
        super.addGestureRecognizer(gestureRecognizer)
        return
    }
}

Above code has also function for disable "PASTE" option in content menu.

Chetan Prajapati
  • 2,249
  • 19
  • 24
0

Could you use the textViewDidChangeSelection method of the UITextViewDelegate protocol?

Jakob W
  • 3,347
  • 19
  • 27