How can I shorten the time "delayTouchesBegan" delays touchesBegan?
Asked Answered
S

3

19

In one of my view controllers I have several views that contain a UITapGestureRecognizer, along with an implementation of touchesBegan. I need to prioritize the taps over touchesBegan so I set the delaysTouchesBegan property of the gesture recognizers to YES. This works correctly, but there's one problem: the gesture recognizer delays touchesBegan for too long. According to the documentation:

When the value of the property is YES, the window suspends delivery of touch objects in the UITouchPhaseBegan phase to the view. If the gesture recognizer subsequently recognizes its gesture, these touch objects are discarded. If the gesture recognizer, however, does not recognize its gesture, the window delivers these objects to the view in a touchesBegan:withEvent: message (and possibly a follow-up touchesMoved:withEvent: message to inform it of the touches’ current locations).

The problem basically is that when the gesture recognizer does not recognize the gesture and delivers these objects to touchesBegan, that operation takes too long. Is there anyway to expedite it, or is it just that the processing of the gesture to determine whether it's a tap is intensive and is impossible to shorten?


Edit:

Here's some more info. This is the code I use to setup the gesture recognizer:

UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
tapRecognizer.cancelsTouchesInView = NO;
tapRecognizer.delaysTouchesBegan = YES;
tapRecognizer.delegate = self;
tapRecognizer.numberOfTapsRequired = 1;
tapRecognizer.numberOfTouchesRequired = 1;
[self.someView addGestureRecognizer:tapRecognizer];
Shonda answered 17/12, 2013 at 6:48 Comment(4)
have update your answer please followProtein
While it may not work for your project a simple way to intercept touches from one view before another is to implement a subview hitTest (i.e. - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event)Olette
@qegal You didn't find my answer useful?Dative
@Sha - Sorry, I've been too busy to test it out. It seems like the best answer so far though so I'll give you the bounty.Shonda
D
28

I'd solve it with changing UITapGestureRecognizer to UILongPressGestureRecognizer. The title of those two is a bit misleading but with UILongPressGestureRecognizer you can set the minimumPressDuration:

The minimum period fingers must press on the view for the gesture to be recognized.

UILongPressGestureRecognizer *tapRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
tapRecognizer.delegate = self;
tapRecognizer.minimumPressDuration = //Up to you;
[self.someView addGestureRecognizer:tapRecognizer];
Dative answered 5/1, 2014 at 9:51 Comment(0)
P
2

Is there anyway to expedite it, or is it just that the processing of the gesture to determine whether it's a tap is intensive and is impossible to shorten?

The delay seems to be dependent on how long it takes to process the gesture, so yes, it is not possible to adjust it (you can log -touchesBegan:withEvent: to see exactly when it is called). For example, if you touch a UIView with a UITapGestureRecognizer and don't move your finger, the tap recognizer still thinks there's a chance you'll lift your finger in that same position, which will be recognized as a tap. So it will keep waiting until you have panned your finger or lifted it. On the other hand if you pan immediately, the UIView gets sent -touchesBegan:withEvent: with almost no delay.

Workarounds that you could try are:

  1. Use the gesture recognizer's cancelTouchesInView property instead. If you set this to YES the UIView will then process touches right away, and then if the gesture recognizer ends up recognizing the touches as a gesture, the view gets sent -touchesCancelled:withEvent:, where you can roll back any changes you made.

  2. Use cancelTouchesInView with an NSTimer in the UIView. Start the NSTimer in the UIView's -touchesBegan:withEvent: and when it fires, perform your action on the view. If the view gets sent -touchesCancelled:withEvent: before the timer fires, cancel the timer.

  3. Subclass UIGestureRecognizer and implement your own tap recognizer :)

I've made an example of options 1 and 2. The example has a view which you can drag around the screen, and the view has a tap recognizer that changes the color of the view. If you tap the view but drag it a little before releasing, you'll see the 'roll back' snapping the view to its position before the touch. If you set the JCPanningView's delayResponseToTouch to YES, you don't see any dragging within the delay period.

This may or may not work for you depending on how your UIViews are handling their touches.

Here is the view controller's interface (JCGestureViewController.h):

#import <UIKit/UIKit.h>

@interface JCGestureViewController : UIViewController
@end

@interface JCPanningView : UIView
@property (nonatomic) BOOL delayResponseToTouch;
- (void)changeColor;
@end

and implementation (JCGestureViewController.m):

#import "JCGestureViewController.h"

@interface JCGestureViewController ()
@property (strong, nonatomic) JCPanningView *panningView;
@end

@implementation JCGestureViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    _panningView = [[JCPanningView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 200.0f, 200.0f)];
    [self.view addSubview:_panningView];

    UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
                                                                                    action:@selector(tappedPanningView)];
    tapRecognizer.cancelsTouchesInView = YES;
    [_panningView addGestureRecognizer:tapRecognizer];

    // set this to YES to have a timer delay the view's response to touches
    _panningView.delayResponseToTouch = NO;
}

- (void)tappedPanningView
{
    [self.panningView changeColor];
}

@end

@interface JCPanningView ()

@property (nonatomic) CGPoint touchBeganLocation;
@property (nonatomic) CGPoint centerWhenTouchBegan;
@property (nonatomic) BOOL respondToTouches;
@property (nonatomic) NSTimer *timer;

@end

@implementation JCPanningView

- (void)dealloc
{
    if (_timer) {
        [_timer invalidate];
        _timer = nil;
    }
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [self randomColor];
    }
    return self;
}

- (void)changeColor
{
    self.backgroundColor = [self randomColor];
}

- (CGFloat)randomRGBValue
{
    return (arc4random() % 255) / 255.0f;
}

- (UIColor *)randomColor
{
    return [UIColor colorWithRed:[self randomRGBValue] green:[self randomRGBValue] blue:[self randomRGBValue] alpha:1.0f];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"touchesBegan:");
    [super touchesBegan:touches withEvent:event];

    UITouch *touch = [touches anyObject];
    self.touchBeganLocation = [touch locationInView:self.superview];
    self.centerWhenTouchBegan = self.center;

    if (self.delayResponseToTouch) {
        if (self.timer) {
            [self.timer invalidate];
        }
        self.timer = [NSTimer scheduledTimerWithTimeInterval:0.2
                                                      target:self
                                                    selector:@selector(startRespondingToTouches)
                                                    userInfo:nil
                                                     repeats:NO];
    }
}

- (void)startRespondingToTouches
{
    self.respondToTouches = YES;
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    self.center = self.centerWhenTouchBegan;

    if (self.timer) {
        [self.timer invalidate];
        self.timer = nil;
    }

    if (self.delayResponseToTouch) {
        self.respondToTouches = NO;
    }
}

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

    if (self.delayResponseToTouch && !self.respondToTouches) {
        return;
    }

    UITouch *touch = [touches anyObject];
    CGPoint newLocation = [touch locationInView:self.superview];
    CGPoint delta = CGPointMake(newLocation.x - self.touchBeganLocation.x, newLocation.y - self.touchBeganLocation.y);
    self.center = CGPointMake(self.centerWhenTouchBegan.x + delta.x, self.centerWhenTouchBegan.y + delta.y);
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    if (self.delayResponseToTouch) {
        self.respondToTouches = NO;
    }
}

@end
Pallette answered 8/1, 2014 at 3:29 Comment(0)
I
-1

For this, when your view is tapped on which you have to delay the touch, you can start a timer with desired time and once time completes you can call the desired method you need.

Isomorphism answered 8/1, 2014 at 5:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.