- Touches
- Recipe: Adding a Simple Direct Manipulation Interface
- Recipe: Adding Pan Gesture Recognizers
- Recipe: Using Multiple Gesture Recognizers Simultaneously
- Recipe: Constraining Movement
- Recipe: Testing Touches
- Recipe: Testing Against a Bitmap
- Recipe: Drawing Touches Onscreen
- Recipe: Smoothing Drawings
- Recipe: Using Multi-Touch Interaction
- Recipe: Detecting Circles
- Recipe: Creating a Custom Gesture Recognizer
- Recipe: Dragging from a Scroll View
- Recipe: Live Touch Feedback
- Recipe: Adding Menus to Views
- Summary
Recipe: Creating a Custom Gesture Recognizer
It takes little work to transform the code shown in Recipe 1-10 into a custom recognizer, as introduced in Recipe 1-11. Subclassing UIGestureRecognizer enables you to build your own circle recognizer that you can add to views in your applications.
Start by importing UIGestureRecognizerSubclass.h into your new class. The file declares everything you need your recognizer subclass to override or customize. For each method you override, make sure to call the original version of the method by calling the superclass method before invoking your new code.
Gestures fall into two types: continuous and discrete. The circle recognizer is discrete. It either recognizes a circle or fails. Continuous gestures include pinches and pans, where recognizers send updates throughout their life cycle. Your recognizer generates updates by setting its state property.
Recognizers are basically state machines for fingertips. All recognizers start in the possible state (UIGestureRecognizerStatePossible), and then for continuous gestures pass through a series of changed states (UIGestureRecognizerStateChanged). Discrete recognizers either succeed in recognizing a gesture (UIGestureRecognizerStateRecognized) or fail (UIGestureRecognizerStateFailed), as demonstrated in Recipe 1-11. The recognizer sends actions to its target each time you update state except when the state is set to possible or failed.
The rather long comments you see in Recipe 1-11 belong to Apple, courtesy of the subclass header file. I’ve included them here because they help explain the roles of the key methods that override their superclass. The reset method returns the recognizer back to its quiescent state, allowing it to prepare itself for its next recognition challenge.
The touches began (and so on) methods are called at similar points as their UIResponder analogs, enabling you to perform your tests at the same touch life cycle points. This example waits to check for success or failure until the touches ended callback, and uses the same testForCircle method defined in Recipe 1-10.
Recipe 1-11. Creating a Gesture Recognizer Subclass
#import <UIKit/UIGestureRecognizerSubclass.h> @implementation CircleRecognizer // called automatically by the runtime after the gesture state has // been set to UIGestureRecognizerStateEnded any internal state // should be reset to prepare for a new attempt to recognize the gesture // after this is received all remaining active touches will be ignored // (no further updates will be received for touches that had already // begun but haven't ended) - (void)reset { [super reset]; points = nil; firstTouchDate = nil; self.state = UIGestureRecognizerStatePossible; } // mirror of the touch-delivery methods on UIResponder // UIGestureRecognizers aren't in the responder chain, but observe // touches hit-tested to their view and their view's subviews // UIGestureRecognizers receive touches before the view to which // the touch was hit-tested - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesBegan:touches withEvent:event]; if (touches.count > 1) { self.state = UIGestureRecognizerStateFailed; return; } points = [NSMutableArray array]; firstTouchDate = [NSDate date]; UITouch *touch = [touches anyObject]; [points addObject: [NSValue valueWithCGPoint: [touch locationInView:self.view]]]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesMoved:touches withEvent:event]; UITouch *touch = [touches anyObject]; [points addObject: [NSValue valueWithCGPoint: [touch locationInView:self.view]]]; } - (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesEnded:touches withEvent: event]; BOOL detectionSuccess = !CGRectEqualToRect(CGRectZero, testForCircle(points, firstTouchDate)); if (detectionSuccess) self.state = UIGestureRecognizerStateRecognized; else self.state = UIGestureRecognizerStateFailed; } @end