Gestures Recognizers in iOS
All we know that the multitouch interface of a iOS enabled devices is superb, and all programmers for such devices know that coding a multitouch interface is a hell. There is no way to do it right. Up until iOS 4, which introduces UIGestureRecognizer.
To use UIGestureRecognizer we need to instantiate it, and configure it, setting one or more handlers, and then we just add the object to the view. That’s all, for instance, we can set this on the -viewDidLoad method of a UIViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UIPinchGestureRecognizer *pinch =
[[UIPinchGestureRecognizer alloc]
initWithTarget:self
action:@selector(pinchGestureCaptured:)];
[self.view addGestureRecognizer:pinch];
[pinch release];
}
As soon as a pinch gesture is recognized, the pinchGestureCaptured: method will be called.
UIGestureRecognizer is an abstract base class. Apple provides a wide family of UIGestureRecognizer subclasses:
| Gesture | UIKit class |
|---|---|
| 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 |
| Touch and hold | UILongPressGestureRecognizer |
I for one can’t imagine a scenario where it might be needed to add a new subclass to the mix, anyhow, UIGestureRecognizer is there to be subclassed.
And we can attach any number of gestures to any UIView. Each gesture recognizer is a state machine that process touches independently. The touches will travel the view hierarchy until they are trapped for a view, and they are delivered to the recognizers. The recognizers will try to, ehem, recognize if the user is performing some gesture, and fire the appropriate selector.
Touches recognizers receives touches to the view they are attached to, and all descendants of that view. For instance, if we have a a container view and two subviews inside it, the container view will get all the touches that hit test successfully between it bounds, and the ones that are attached to the subviews.
Selectors
As we have seen in the snippet above, the initializer of a gesture recognizer takes a target and a selector, and the selector is defined to receive a UIGestureRecognizer subclass. This is really important, because embedded in this object we will receive important information regarding the gesture itself. For instance, inside the pinchGestureCaptured selector we have set before, we can use the scale or the velocity properties of the gesture to know what we need to do.
- (void)pinchGestureCaptured:
(UIPinchGestureRecognizer)gesture
{
gesture.scale;
gesture.velocity;
}
Obviously, for other gesture recognizers, the set of methods and properties to perform some sort of introspection varies.
Delegate
Ok, we’ve mentioned that UIGestureRecognizer is there to be subclassed. But there is well known pattern in Cocoa to tweak a class without incur in the work of a full subclass: delegates. And yes, there is also a UIGestureRecognizerDelegate protocol. This protocol defines three different methods:
– gestureRecognizerShouldBegin:
– gestureRecognizer:shouldReceiveTouch:
– gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:
– gestureRecognizerShouldBegin: allows us to define, at the very last moment, if a gesture recognizer is or is not going to be recognized. This is useful to filter interactions on the run.
– gestureRecognizer:shouldReceiveTouch: might prevent a gesture recognizer to even see the touch in the first place.
We will talk about – gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: in the next section.
Conflict resolution
The system supports, at most, a single sequence of touch input. Once a recognizer match a gesture, it fires the handler, end of the story. But, what happens, for instance, when two views have attached, let’s say, a UISwipeGestureRecognizer. Well, the front-most view (the first to hit test true), is the one that gets the gesture. This scheme is reasonable for almost any use scenario. For instance we might attach a UITapGestureRecognizer for both, a one tap gesture and a double tap gesture to the same view. The system will fires first the handler for the one tap gesture, and then the double tap. This is reasonable in the case that the two gestures are stackable, ie, one is the prelude for the other. For instance, in a text based interface, the one tap gesture might be used to move the cursor to a point in the view, and the double tap might be used to select the word underneath the tap. But what if the single tap gesture should activate a behavior completely independent of a double tap gesture? Let’s say a single tap gesture should produce a new view, and a double tap should present a popover with some details. In this case, the two gestures are mutable exclusive. In this case we can use - requireGestureRecognizerToFail: This method is call from the first gesture to be recognized, to the second. For instance, in our example, when we need to validate a single tap against the fail of a double tap gesture, we can call the method like this:
[singleTap requireGestureRecognizerToFail:doubleTap];
If we call this method on singleTap then the gesture recognizer will recognize the single tap, but it will not fires the handler up until doubleTap fails. If doubleTap succeed, then singleTap will be aborted. If on the other hand, doubleTap fails to recognize a gesture, then singleTap fires its handler.
Anyhow, it is recommended not to use -requireGestureRecognizerToFail: because it introduces some latency to the interface flow. It is better to organize the information flow in a way that gestures might be stacked.
As mentioned, gestures fires one at a time, but what if we want two gestures to be active at the same time? Well, – gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: comes to the rescue. We need to implement this delegate method to let the system knows that we want the two gestures to be alive at the same time.
Sample Code
You can find a iOS application demoing some of the concepts of Gestures Recognizer in GitHub