Detecting Multitouch Gestures in iOS
Before iOS 3.2 was introduced on the iPad (and latter with iOS 4.0 on the iPhone and iPod Touch), interpreting touch events as different gestures was an awkward task, and relied upon subclassing UIView, and then implementing the following methods.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
If you were trying to detect if a user was dragging a view (or panning as Apple would say), you would usually implement the touchesBegan:withEvent: to start dragging the piece, touchesMoved:withEvent: to adjust the view’s frame, and then touchesEnded:withEvent: and touchesCancelled:withEvent: to stop detecting the drag. While implementing dragging was not too complex, trying to detect a more advance gesture such as a swipe or pinch was a lot more complex … and a lot of hard work to make it replicates Apple’s version of the gestures accurately.
Thankfully Apple created UIGestureRecognizer:
UIGestureRecognizer is an abstract base class for concrete gesture-recognizer classes. A gesture-recognizer object (or, simply, a gesture recognizer) decouples the logic for recognizing a gesture and acting on that recognition. When one of these objects recognizes a common gesture or, in some cases, a change in the gesture, it sends an action message to each designated target object. Taken from Apple
As UIGestureRecognizer is an abstract class, you will never use it directly. You will however use one of its subclasses, that have been designed specifically to capture a specific gesture.
objc
UITapGestureRecognizer
UIPinchGestureRecognizer
UIRotationGestureRecognizer
UISwipeGestureRecognizer
UIPanGestureRecognizer
UILongPressGestureRecognizer
So lets take UIPanGestureRecognizer for an example, and we will use it to move a view around.
Our example view controller has a view (obviously), and this has a subview called panView, which is the view we will move around.
First thing that you will need to do, is create a pan gesture recognizer (UIPanGestureRecognizer) and add it to the panView. This will allow you to detect the gesture. To get informed about the gesture getting detected, you have to set the view controller as the delegate, and then have it call the the** panViewWithGestureRecognizer:** selector. (Note: You can set any object as the delegate, as long as it implements UIGestureRecognizerDelegate protocol. For this example the view controller makes sence.)
UIPanGestureRecognizer *panGesture = nil;
panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panViewWithGestureRecognizer:)];
[panGesture setMaximumNumberOfTouches:2];
[panGesture setDelegate:self];
[panView addGestureRecognizer:panGesture];
[panGesture release];
panViewWithGestureRecognizer: will get called when the gesture changes state, the possible states are:
UIGestureRecognizerStatePossible
UIGestureRecognizerStateBegan
UIGestureRecognizerStateChanged
UIGestureRecognizerStateEnded
UIGestureRecognizerStatePossible
UIGestureRecognizerStateCancelled
UIGestureRecognizerStateFailed
UIGestureRecognizerStateRecognized
UIGestureRecognizerStateEnded
For this example we only care about if gesture began (UIGestureRecognizerStateBegan) or has changed (UIGestureRecognizerStateChanged). When a pan gesture happens (begins or changes) we want to move the center of panView to be underneath the users finger, thus dragging the piece.
To do this we implement panViewWithGestureRecognizer: in the following way.
- (void)panViewWithGestureRecognizer:(UIPanGestureRecognizer *)gestureRecognizer
{
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan
|| [gestureRecognizer state] == UIGestureRecognizerStateChanged)
{
CGPoint translation = [gestureRecognizer translationInView:[panView superview]];
[piece setCenter:CGPointMake([panView center].x + translation.x, [panView center].y + translation.y)];
[gestureRecognizer setTranslation:CGPointZero inView:[panView superview]];
}
}
What makes this super easy is the convenience methodtranslationInView:, which is found on UIPanGestureRecognizer. This gives you the value of where the center of the panView should be, without having to calculate it yourself. We then need to set the translation back to zero using setTranslation:, so when we calculate the next gesture, it will be from the new position of the panView. And thats all there is to it.
As mentioned before there are a few gesture recogniser that have already been prebuilt for you to use, and you can even add multiple gesture recogniser to the same view. If you do this, you will want to implement:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;
This simply allows you to specify if a gesture should be detected alongside another gesture e.g. a pinch and a rotation gesture. If Apple’s pre-built gesture recognizers are not enough for you, you can always create your own subclass of UIGestureRecognizer.