Question

I am trying to get touchesEnded to fire after a gesture. I've included a code snippet where both touchesBegan and touchesEnded are enabled to fire. I get two odd behaviours with the code below. When I pan it prints begin and swiped ended. When I touch the canvas I get a begin and two end messages.

I've tried removing the touchesBegan to be sure it doesn't conflict with touchesEnded but the behaviour is the same.

#import "C4WorkSpace.h"

@implementation C4WorkSpace

-(void)setup
{
    [self addGesture:PAN name:@"pan" action:@"bbb:"];
    [self gestureForName:@"pan"].delaysTouchesBegan = NO;
    [self gestureForName:@"pan"].delaysTouchesEnded = NO;
}

-(void) bbb : (UIGestureRecognizer *) recognizer
{
    if(recognizer.state == UIGestureRecognizerStateEnded)
        C4Log(@"Swipe Ended");
}
-(void) touchesBegan
{
    C4Log(@"Begin");
}
-(void) touchesEnded
{
    C4Log(@"End");
}

@end
Was it helpful?

Solution

This is an issue that deals directly with the way that UIGestures are architected to work. The delaysTouchesEnded basically delays the sending of a message to touchesEnded:withEvent: and setting this value to NO doesn't necessarily mean that the event will be triggered.

From the docs:

If the gesture recognizer subsequently recognizes its gesture, these touch objects are cancelled (via a touchesCancelled:withEvent: message). If the gesture recognizer does not recognize its gesture, the window delivers these objects in an invocation of the view’s touchesEnded:withEvent: method. Set this property to NO to have touch objects in the UITouchPhaseEnded delivered to the view while the gesture recognizer is analyzing the same touches.

In your code snippet the touchesBegan and touchesEnded are NOT actually enabled to fire. What is happening is you're disabling the "delay" on whether or not the gesture will allow the touchesBegan or touchesEnded to be available for firing.

With delaysTouchesBegan set to NO, the following happens:

  1. touch happens
  2. gesture intercepts
  3. gesture is recognized
  4. touchesBegan actually happened (because the gesture recognized)
  5. no delay, touchesBegan fires

With delaysTouchesEnded the firing of touchesEnded is contingent on whether or not the gesture successfully completes... Unlike the previous case where touchesBegan always actually happens at the beginning of the gesture.

What happens in this case is the following:

  1. gesture is being recognized
  2. gesture completes successfully
  3. gesture CANCELS touches for the view in which the gesture happens (this is expected behaviour of UIGestureRecognizer

... touchesEnded happens when:

  1. gesture is being recognized
  2. gesture completes UNSUCCESSFULLY
  3. touchesEnded is fired

With your code if you touch down, hold the gesture without moving your finger, and then release after a little while the touchesEnded gets fired. The reason is that the PAN doesn't complete successfully and allows the touchesEnded to fire.

A different approach

You are working with a gesture, so any interaction you want to have happen should take into consideration the gesture you're working with... That is, when you start working with a gesture try and think in terms of the gesture, knowing that it is going to come between the view you're touching and its inherent touchesBegan, etc., methods.

Bottom Line

Your bbb: method is perfect.

When working with gestures, pipe through a method like this to determine the various states of the gesture. This is how you want to work with gestures.

Try the following code:

#import "C4WorkSpace.h"

@implementation C4WorkSpace
-(void)setup
{
    [self addGesture:PAN name:@"pan" action:@"bbb:"];
    [self gestureForName:@"pan"].delaysTouchesBegan = NO;
    [self gestureForName:@"pan"].delaysTouchesEnded = NO;
}

-(void) bbb : (UIGestureRecognizer *) recognizer {
    if(recognizer.state == UIGestureRecognizerStateBegan) {
        C4Log(@"PAN Begin");
    }
    if(recognizer.state == UIGestureRecognizerStateEnded) {
        C4Log(@"PAN ended");
    }
}

-(void)touchesBegan {
    C4Log(@"A touch began");
}

-(void)touchesEnded {
    C4Log(@"A touch ended");
}

-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    C4Log(@"A touch cancelled");
}

@end

Notice that the TOUCH event gets cancelled after you BEGIN the gesture? This is why touchesEnded will never be fired, because when the GESTURE begins the system recognizes that the "touch" isn't really a touch and is really a gesture.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top