UIGestureRecognizer Tutorial in iOS 5: Pinches, Pans, and More!
Update 7/17/14: We now have a new version of this tutorial fully updated to Swift – check it out! If you need to detect gestures in your app, such as taps, pinches, pans, or rotations, it’s extremely easy with the built-in UIGestureRecognizer classes. In this tutorial, we’ll show you how you can easily add gesture […] By Ray Wenderlich.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
UIGestureRecognizer Tutorial in iOS 5: Pinches, Pans, and More!
25 mins
UIGestureRecognizer Dependencies
It works pretty well, except there’s one minor annoyance. If you drag an object a very slight amount, it will pan it and play the sound effect. But what we really want is to only play the sound effect if no pan occurs.
To solve this we could remove or modify the delegate callback to behave differently in the case a touch and pinch coincide, but I wanted to use this case to demonstrate another useful thing you can do with gesture recognizers: setting dependencies.
There’s a method called requireGestureRecognizerToFail that you can call on a gesture recognizer. Can you guess what it does? ;]
Let’s try it out. Open MainStoryboard.storyboard, open up the Assistant Editor, and makes ure that ViewController.h is showing there. Then control-drag from the monkey pan gesture recognizer to below the @interface, and connect it to an outlet named monkeyPan. Repeat this for the banana pan gesture recognizer, but name the outlet bananaPan.
Then simply add these two lines to viewDidLoad, right before the TODO:
[recognizer requireGestureRecognizerToFail:monkeyPan];
[recognizer requireGestureRecognizerToFail:bananaPan];
Now the tap gesture recognizer will only get called if no pan is detected. Pretty cool eh? You might find this technique useful in some of your projects.
Custom UIGestureRecognizer
At this point you know pretty much everything you need to know to use the built-in gesture recognizers in your apps. But what if you want to detect some kind of gesture not supported by the bulit-in recognizers?
Well, you could always write your own! Let’s try it out by writing a very simple gesture recognizer to detect if you try to “tickle” the monkey or banana by moving your finger several times from left to right.
Create a new file with the iOS\Cocoa Touch\Objective-C class template. Name the class TickleGestureRecognizer, and make it a subclass of UIGestureRecognizer.
Then replace TickleGestureRecognizer.h with the following:
#import <UIKit/UIKit.h>
typedef enum {
DirectionUnknown = 0,
DirectionLeft,
DirectionRight
} Direction;
@interface TickleGestureRecognizer : UIGestureRecognizer
@property (assign) int tickleCount;
@property (assign) CGPoint curTickleStart;
@property (assign) Direction lastDirection;
@end
Here we are declaring three properties of info we need to keep track of to detect this gesture. We’re keeping track of:
- tickleCount: How many times the user has switched the direction of their finger (while moving a minimum amount of points). Once the user moves their finger direction 3 times, we count it as a tickle gesture.
- curTickleStart: The point where the user started moving in this tickle. We’ll update this each time the user switches direction (while moving a minimum amount of points).
Of course, these properties here are specific to the gesture we’re detecting here – you’ll have your own if you’re making a recognizer for a different type of gesture, but you can get the general idea here.
Now switch to TickleGestureRecognizer.m and replace it with the following:
#import "TickleGestureRecognizer.h"
#import <UIKit/UIGestureRecognizerSubclass.h>
#define REQUIRED_TICKLES 2
#define MOVE_AMT_PER_TICKLE 25
@implementation TickleGestureRecognizer
@synthesize tickleCount;
@synthesize curTickleStart;
@synthesize lastDirection;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch * touch = [touches anyObject];
self.curTickleStart = [touch locationInView:self.view];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
// Make sure we've moved a minimum amount since curTickleStart
UITouch * touch = [touches anyObject];
CGPoint ticklePoint = [touch locationInView:self.view];
CGFloat moveAmt = ticklePoint.x - curTickleStart.x;
Direction curDirection;
if (moveAmt < 0) {
curDirection = DirectionLeft;
} else {
curDirection = DirectionRight;
}
if (ABS(moveAmt) < MOVE_AMT_PER_TICKLE) return;
// Make sure we've switched directions
if (self.lastDirection == DirectionUnknown ||
(self.lastDirection == DirectionLeft && curDirection == DirectionRight) ||
(self.lastDirection == DirectionRight && curDirection == DirectionLeft)) {
// w00t we've got a tickle!
self.tickleCount++;
self.curTickleStart = ticklePoint;
self.lastDirection = curDirection;
// Once we have the required number of tickles, switch the state to ended.
// As a result of doing this, the callback will be called.
if (self.state == UIGestureRecognizerStatePossible && self.tickleCount > REQUIRED_TICKLES) {
[self setState:UIGestureRecognizerStateEnded];
}
}
}
- (void)resetState {
self.tickleCount = 0;
self.curTickleStart = CGPointZero;
self.lastDirection = DirectionUnknown;
if (self.state == UIGestureRecognizerStatePossible) {
[self setState:UIGestureRecognizerStateFailed];
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self resetState];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[self resetState];
}
@end
There’s a lot of code here, but I’m not going to go over the specifics because frankly they’re not quite important. The important part is the general idea of how it works: we’re implemeneting touchesBegan, touchesMoved, touchesEnded, and touchesCancelled and writing custom code to look at the touches and detect our gesture.
Once we’ve found the gesture, we want to send updates to the callback method. You do this by switching the state of the gesture recognizer. Usually once the gesture begins, you want to set the state to UIGestureRecognizerStateBegin, send any updates with UIGestureRecognizerStateChanged, and finalize it with UIGestureRecognizerStateEnded.
But for this simple gesture recognizer, once the user has tickled the object, that’s it – we just mark it as ended. The callback will get called and we can implement the code there.
OK, now let’s use this new recognizer! Open ViewController.h and make the following changes:
// Add to top of file
#import "TickleGestureRecognizer.h"
// Add after @interface
@property (strong) AVAudioPlayer * hehePlayer;
- (void)handleTickle:(TickleGestureRecognizer *)recognizer;
And to ViewController.m:
// After @implementation
@synthesize hehePlayer;
// In viewDidLoad, right after TODO
TickleGestureRecognizer * recognizer2 = [[TickleGestureRecognizer alloc] initWithTarget:self action:@selector(handleTickle:)];
recognizer2.delegate = self;
[view addGestureRecognizer:recognizer2];
// At end of viewDidLoad
self.hehePlayer = [self loadWav:@"hehehe1"];
// Add at beginning of handlePan (gotta turn off pan to recognize tickles)
return;
// At end of file
- (void)handleTickle:(TickleGestureRecognizer *)recognizer {
[self.hehePlayer play];
}
So you can see that using this custom gesture recognizer is as simple as using the built-in ones!
Compile and run and “he he, that tickles!”