Pages

Friday, September 10, 2010

Detecting tap and double-tap with Gesture Recognizers

Now that I am finished with my book I am getting back to work on writing apps. In the original design for the current app that I am working on, I wanted a UIView that responded to taps and double taps. If you don’t already, know, as of iOS 3.2 there is a new way to handle user interaction events called gesture recognizers.

Gesture recognizers are basically exactly what they sound like; classes that you can use to recognize when a user has performed a gesture. Using gesture recognizers simplifies event handling because you do not have to write all of the code to track touches like you would have done in the past. You can simply create an instance of a gesture recognizer, configure it, and add it to a view. Then, when the gesture recognizer recognizes a gesture, it simply calls back a selector that you define.

UIGestureRecognizer is an abstract base class that you can use as a starting point to implement your own gesture recognizer. In addition to providing this base class, the iOS SDK provides several concrete UIGestureRecognizer subclass implementations that you can use to recognize the following gestures as documented in the iOS documentation:

Tapping (any number of taps)

UITapGestureRecognizer

Pinching in and out (for zooming a view)        

UIPinchGestureRecognizer

Panning or dragging

UIPanGestureRecognizer

Swiping (in any direction)        

UISwipeGestureRecognizer

Rotating (fingers moving in opposite directions)

UIRotationGestureRecognizer

Long press (also known as “touch and hold”)                

UILongPressGestureRecognizer

In my case, I wanted to create two different actions, one for when a user tapped a view and another for when the user double-tapped. This may seem straight forward as you can configure the UITapGestureRecognizer to call a selector when it detects a specific number of taps by setting the numberOfTapsRequired property. So, my idea was to use a UITapGestureRecognizer with numberOfTapsRequired set to 1 to call a selector called handleSingleTap and a second UITapGestureRecognizer with numberOfTapsRequired set to 2 to call a selector called handleDoubleTap.

This was all well and good. When I built and ran my application, single tap event handling worked like a charm. Every time I tapped on the view, I saw the behavior that I expected. The problem occurred when I tried to double tap. The double tap selector was called as expected, however, the single tap selector was always called first. I guess that, in hindsight, I should have expected this as it is impossible to tap twice without tapping once. One particularly important thing that you should keep in mind when using gesture recognizers is that they are not a part of the responder chain. If you are relying on handling events using the responder chain and you introduce gesture recognizers, events that are recognized by the gesture recognizer will not be sent through the responder chain.

What I needed was a way to set up a relationship between the two gesture recognizers. I needed to be able to detect if a double tap was happening and if it was, to ignore the single tap gesture. Fortunately, Apple thought ahead and designed a way to do this into the architecture. This is a case where you only want the single tap selector called if the double tap gesture recognizer fails. To implement this, you send the message requireGestureRecognizerToFail: to the single tap gesture recognizer with the double tap recognizer as its argument. This tells the single tap gesture recognizer to wait to fire until the double tap gesture recognizer transitions to the UIGestureRecognizerStateFailed state. Doing this solved the problem. At this point, the correct selector was called when a double tap was recognized and the single tap selector was not called.

However, this caused another issue. The issue was that when a user would single tap, the action that would result from a single tap was delayed as the single tap gesture recognizer was forced to wait to ensure that the double tap gesture recognizer failed before calling the single tap selector. This behavior is documented in the Event Handling Guide for iPhone OS. For many applications, this may be acceptable, however the lag between a single tap and the gesture recognizer calling its selector was too long for my application. I searched for a way to adjust the timeout for a gesture recognizer to transition to UIGestureRecognizerStateFailed to no avail. It seemed that the only way to get the behavior that I wanted was to write some custom gesture recognizers.

In the end, I decided to not use the double click gesture recognizer and to handle my user interactions differently by not requiring a double tap at all. So, keep in mind that it is possible to set up dependencies between gesture recognizers however when implementing discrete behaviors for single and double tap, you may want to go a different route. Gesture recognizers are a convenient way to react to user input and can be configured in a variety of way depending on the needs of your application. If worst comes to worst, you can always write your own concrete subclass of UIGestureRecognizer.