UIGestureRecognizer Cancelled State

UIGestureRecognizers were a great improvement to iOS starting in version 3.2. For those of you who had to deal with the touchesBegan: and touchesMoved: method of UIResponder you need no explanation. For the noobs out there, they provide a common interface for standard touch events and give a simple API to access what it means. For rotations that means that complex trigonometry of rotation calculations are done for you and all you have to do is call

    [rotationRecongizer rotation];

to get the rotation begin performed on the UIView and you can easy append it to the transform.

Something that may confuse people is the many states that a recognizer can be in:

typedef enum {
    UIGestureRecognizerStatePossible,   // the recognizer has not yet recognized its gesture, but may be evaluating touch events. this is the default state
    
    UIGestureRecognizerStateBegan,      // the recognizer has received touches recognized as the gesture. the action method will be called at the next turn of the run loop
    UIGestureRecognizerStateChanged,    // the recognizer has received touches recognized as a change to the gesture. the action method will be called at the next turn of the run loop
    UIGestureRecognizerStateEnded,      // the recognizer has received touches recognized as the end of the gesture. the action method will be called at the next turn of the run loop and the recognizer will be reset to UIGestureRecognizerStatePossible
    UIGestureRecognizerStateCancelled,  // the recognizer has received touches resulting in the cancellation of the gesture. the action method will be called at the next turn of the run loop. the recognizer will be reset to UIGestureRecognizerStatePossible
    
    UIGestureRecognizerStateFailed,     // the recognizer has received a touch sequence that can not be recognized as the gesture. the action method will not be called and the recognizer will be reset to UIGestureRecognizerStatePossible
    
    // Discrete Gestures – gesture recognizers that recognize a discrete event but do not report changes (for example, a tap) do not transition through the Began and Changed states and can not fail or be cancelled
    UIGestureRecognizerStateRecognized = UIGestureRecognizerStateEnded // the recognizer has received touches recognized as the gesture. the action method will be called at the next turn of the run loop and the recognizer will be reset to UIGestureRecognizerStatePossible
} UIGestureRecognizerState;

UIGestureRecognizerStatePossible and UIGestureRecognizerStateFailed are rarely relevant since they do not call the action method. UIGestureRecognizerStateRecognized is also rarely relevant since it is only for discreet touch events, such as a tap. (Update: these states are very relevant to subclassers. They will control such functions as requireGestureRecognizerToFail:. But these are not so relevant for those using Apple's built in GestureRecognizers)

In general the action method for the gestureRecognizer will test the recognizer's state and have different code for UIGestureRecognizerStateBegan, UIGestureRecognizerStateChanged and UIGestureRecognizerStateEnded. UIGestureRecognizerStateCancelled is often ignored - it shouldn't be.

Many people are confused what that cancelled state is, or why it would ever be used. The cancelled state is rarely seen, but if a gesture is cancelled it may leave the UIView in an undesirable state. My rule is to run the same code for the Cancelled State as I do for Ended State.

Let me give you an example of what I mean. Take a look at the Facebook app - this will work on either the iPhone or the iPad version, but you need iOS 5. The main view of the Facebook app can be panned to the right to reveal a menu below it. Start your finger close the top center of the screen and start dragging right. Now move you finger up to the status and pull down - as if you are trying to pull down the iOS notifications. If you did it right (it takes a little practice) you now have the Facebook sidebar half revealed and half hidden. (You can also start with the menu revealed and start panning left, then move your finger up and pull down).

When you start moving your finger it trigger the UIPanGestureRecognizer to start moving the UIView. But after you start moving down near the status bar iOS says "oops I was wrong" and cancels the GestureRecognizers using those touches.

I don't need to see Facebook's code to know that they failed to implement the same code for UIGestureRecognizerStateCancelled as they did for UIGestureRecognizerStateEnded. In truth, Facebook should implement different code for the Cancelled State to return the UIView to where it started regardless of how it has been moved by the gesture, but that is a lot to ask for a case which is so rare. Leaving the view half open and half close though is much worse, especially considering how easy it is to fix.

So don't make the same mistake as Facebook, go through all your code and replace

	if (UIGestureRecognizerStateEnded == [recognizer state])

with

	if (UIGestureRecognizerStateCancelled ==[recognizer state] || UIGestureRecognizerStateEnded == [recognizer state])

Submit a Comment