iOS Multi-Touch Not Working - ios

I have the regular OpenGL / EAGL setup going on:
#interface EAGLView : UIView {
#public
EAGLContext* context;
}
#property (nonatomic, retain) EAGLContext* context;
#end
#implementation EAGLView
#synthesize context;
+ (Class)layerClass {
return [CAEAGLLayer class];
}
#end
#interface EAGLViewController : UIViewController {
#public
EAGLView* glView;
}
#property(nonatomic, retain) EAGLView* glView;
#end
#implementation EAGLViewController
#synthesize glView;
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
for (UITouch* touch in touches) {
CGPoint location = [touch locationInView:glView];
int index;
for (index = 0; index < gCONST_CURSOR_COUNT; ++index) {
if (sCursor[index] == NULL) {
sCursor[index] = touch;
break;
}
}
}
[super touchesBegan:touches withEvent:event];
}
That implementation includes corresponding touchesEnded/Canceled/Moved as well. The code fully works and tracks well.
I also make sure that I'm giving proper values for everything:
sViewController = [EAGLViewController alloc];
CGRect rect = [[UIScreen mainScreen] applicationFrame];
sViewController.glView = [[EAGLView alloc] initWithFrame:CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height)];
Assert(sViewController.glView);
sViewController.glView.userInteractionEnabled = YES;
sViewController.glView.multipleTouchEnabled = YES;
sViewController.glView.exclusiveTouch = YES;
It all compiles just fine, but I'm never receiving more than one UITouch. I don't mean in a single touchesBegan, but the index never goes past 0. I also set a breakpoint for the second time it enters that function, and putting two fingers on doesn't make it trigger.

If you want to detect multiple touches (and/or distinguish between a one finger, two finger etc. touch), try using a UIPanGestureRecognizer. When you set it up, you can specify the minimum and maximum number of touches. Then attach it to the view where you want to detect the touches. When you receive events from it, you can ask it how many touches it received and branch accordingly.
Here's the apple documentation:
http://developer.apple.com/library/ios/#documentation/uikit/reference/UIPanGestureRecognizer_Class/Reference/Reference.html
If you do this, you might not need to use the touchesBegan/Moved/Ended methods at all and, depending on how you set up the gesturerecognizer, touchesBegan/Moved/Ended may never get called.

Use [event allTouches] in place of touches. touches represents only the touches that have 'changed'. From the apple docs:
If you are interested in touches that have not changed since the last
phase or that are in a different phase than the touches in the
passed-in set, you can find those in the event object. Figure 3-2
depicts an event object that contains touch objects. To get all of
these touch objects, call the allTouches method on the event object.

It seems all I was missing was this:
sViewController.view = sViewController.glView;

Related

How to send the touch events of UIScrollView to the view behind it?

i have a transparent UIScrollView on top of another view.
the scroll view has content - text and images, used to display info.
the view behind it has some images that the user should be able to tap on.
and the content over them is scrollable using the mentioned scrollview.
i want to be able to normally use the scroll view (no zoom though), but when that scroll view is not actually scrolling to let the tap events through to the view behind it.
using a combination of the touch and scroll events i can determine when to let the taps through.
but the view behind it still it does not receive them.
i have tried using something like this for all touch events:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"touchesBegan %#", (_isScrolling ? #"YES" : #"NO"));
if(!_isScrolling)
{
NSLog(#"sending");
[self.viewBehind touchesBegan:touches withEvent:event];
}
}
but it does not work.
also in my case i cannot really apply the hitTest and pointInside solutions, given the use case that i have.
First off UIScrollViews only inherently recognize UIPanGestureRecognizers and UIPinchGestureRecognizers so you need to add a UITapGestureRecognizer to the UIScrollView so it can recognize any tapping gestures as well:
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
// To prevent the pan gesture of the UIScrollView from swallowing up the
// touch event
tap.cancelsTouchesInView = NO;
[scrollView addGestureRecognizer:tap];
Then once you receive that tap gesture and the handleTap: action is triggered, you can use locationInView: to detect whether the tap gesture's position is in fact within the frame of one of the images below your scroll view, for example:
- (void)handleTap:(UITapGestureRecognizer *)recognizer {
// First get the tap gesture recognizers's location in the entire
// view's window
CGPoint tapPoint = [recognizer locationInView:self.view];
// Then see if it falls within one of your below images' frames
for (UIImageView* image in relevantImages) {
// If the image's coordinate system isn't already equivalent to
// self.view, convert it so it has the same coordinate system
// as the tap.
CGRect imageFrameInSuperview = [image.superview convertRect:image toView:self.view]
// If the tap in fact lies inside the image bounds,
// perform the appropriate action.
if (CGRectContainsPoint(imageFrameInSuperview, tapPoint)) {
// Perhaps call a method here to react to the image tap
[self reactToImageTap:image];
break;
}
}
}
This way, the above code is only performed if a tap gesture is recognized, your program only reacts to a tap on the scroll view if the tap location falls within an image; otherwise, you can just scroll your UIScrollView as usual.
Here I present my complete solution that:
Forwards touches directly to views instead of calling a control event.
User can specify which classes to forward.
User can specify which views to check if forward is needed.
Here the interface:
/**
* This subclass of UIScrollView allow views in a deeper Z index to react when touched, even if the scrollview instance is in front of them.
**/
#interface MJForwardingTouchesScrollView : UIScrollView
/**
* Set of Class objects. The scrollview will events pass through if the initial tap is not over a view of the specified classes.
**/
#property (nonatomic, strong) NSSet <Class> *forwardsTouchesToClasses;
/**
* Optional array of underlying views to test touches forward. Default is nil.
* #discussion By default the scroll view will attempt to forward to views located in the same self.superview.subviews array. However, optionally by providing specific views inside this property, the scroll view subclass will check als among them.
**/
#property (nonatomic, strong) NSArray <__kindof UIView*> *underlyingViews;
#end
And the Implementation:
#import "MJForwardingTouchesScrollView.h"
#import "UIView+Additions.h"
#implementation MJForwardingTouchesScrollView
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self != nil)
{
_forwardsTouchesToClasses = [NSSet setWithArray:#[UIControl.class]];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self != nil)
{
_forwardsTouchesToClasses = [NSSet setWithArray:#[UIControl.class]];
}
return self;
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
BOOL pointInside = [self mjz_mustCapturePoint:point withEvent:event];
if (!pointInside)
return NO;
return [super pointInside:point withEvent:event];
}
#pragma mark Private Methods
- (BOOL)mjz_mustCapturePoint:(CGPoint)point withEvent:(UIEvent*)event
{
if (![self mjz_mustCapturePoint:point withEvent:event view:self.superview])
return NO;
__block BOOL mustCapturePoint = YES;
[_underlyingViews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (![self mjz_mustCapturePoint:point withEvent:event view:obj])
{
mustCapturePoint = NO;
*stop = YES;
}
}];
return mustCapturePoint;
}
- (BOOL)mjz_mustCapturePoint:(CGPoint)point withEvent:(UIEvent *)event view:(UIView*)view
{
CGPoint tapPoint = [self convertPoint:point toView:view];
__block BOOL mustCapturePoint = YES;
[view add_enumerateSubviewsPassingTest:^BOOL(UIView * _Nonnull testView) {
BOOL forwardTouches = [self mjz_forwardTouchesToClass:testView.class];
return forwardTouches;
} objects:^(UIView * _Nonnull testView, BOOL * _Nullable stop) {
CGRect imageFrameInSuperview = [testView.superview convertRect:testView.frame toView:view];
if (CGRectContainsPoint(imageFrameInSuperview, tapPoint))
{
mustCapturePoint = NO;
*stop = YES;
}
}];
return mustCapturePoint;
}
- (BOOL)mjz_forwardTouchesToClass:(Class)class
{
while ([class isSubclassOfClass:NSObject.class])
{
if ([_forwardsTouchesToClasses containsObject:class])
return YES;
class = [class superclass];
}
return NO;
}
#end
The only extra code used is inside the UIView+Additions.h category, which contains the following method:
- (void)add_enumerateSubviewsPassingTest:(BOOL (^_Nonnull)(UIView * _Nonnull view))testBlock
objects:(void (^)(id _Nonnull obj, BOOL * _Nullable stop))block
{
if (!block)
return;
NSMutableArray *array = [NSMutableArray array];
[array addObject:self];
while (array.count > 0)
{
UIView *view = [array firstObject];
[array removeObjectAtIndex:0];
if (view != self && testBlock(view))
{
BOOL stop = NO;
block(view, &stop);
if (stop)
return;
}
[array addObjectsFromArray:view.subviews];
}
}
Thanks
The problem is that your UIScrollView is consuming the event. To pass it through, you would have to disable the user's interaction on it, but it wouldn't scroll then. If you have the touches location however, you can calculate where would that fall on the underlying view, using the convertPoint:toView: method, and call a mathod on it by passing on the CGPoint. From there, you can calculate which image was tapped.

Detecting a touch anywhere on the screen

I am wanting to know when a user has touched anywhere on the screen of my app.
I have looked into using -(UIResponder *)nextResponder but unfortunately this will not work, as I am also reloaded a table automatically, so this gets trigged when that occurs.
I have also tried a gesture recognizer, with the following code. But this will only recognise touches on the view. Where as I have many buttons the user will be using to operate the app. I would like to avoid adding a gesture recogniser or code for this in every button and segment control I have on the screen
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapOnView:)];
[self.mainView addGestureRecognizer:tap];
- (void)tapOnView:(UITapGestureRecognizer *)sender
{
//do something
}
I have also tried -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event , but this has the same issue as the gesture recognizer.
I was wondering if there is any way I could achieve this task. I was hoping that I may be able to recognise the type of event from within the nextResponder, and then I could detect if it is button for example.
EDIT: The reason I am working on this is that my app needs to stay active and the screen cannot be locked (so I have disabled screen locking). To avoid excessive use of power, I need to dim the screen, but then return the brightness back to the original level once the app is touched. I need this feature to only occur on 1 of my viewcontrollers.
As mentioned by Ian MacDonald, using hitTest:: is a great solution to detect user interaction on an app wide scale, including when buttons, textfields, etc, are selected.
My solution was to subclass UIWindow and implement the hitTest method.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// do your stuff here
// return nil if you want to prevent interaction with UI elements
return [super hitTest:point withEvent:event];
}
You could attach your UITapGestureRecognizer to your [[UIApplication sharedApplication] keyWindow].
Alternatively, you could override hitTest: of your root UIView.
Is there a particular task you are hoping to accomplish? There may be a better way than assigning an "anywhere" gesture.
Edit: Use hitTest:.
#interface PassthroughView : UIView
#property (readonly) id target;
#property (readonly) SEL selector;
#end
#implementation PassthroughView
- (void)setTarget:(id)target selector:(SEL)selector {
_target = target;
_selector = selector;
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
[_target performSelector:_selector];
return nil;
}
#end
#implementation YourUIViewController {
PassthroughView *anytouchView;
}
- (void)viewDidLoad {
// Add this at the end so it's above all other views.
anytouchView = [[PassthroughView alloc] initWithFrame:self.view.bounds];
[anytouchView setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
[anytouchView setTarget:self selector:#selector(undim)];
[anytouchView setHidden:YES];
[self.view addSubview:anytouchView];
}
- (void)undim {
[anytouchView setHidden:YES];
}
- (void)dim {
[anytouchView setHidden:NO];
}
#end
Your edit adds more clarity to your question.
The reason I am working on this is that my app needs to stay active
and the screen cannot be locked (so I have disabled screen locking).
To avoid excessive use of power, I need to dim the screen, but then
return the brightness back to the original level once the app is
touched.
Since you are controlling the screen brightness, you can add one transparent view controller before dimming screen on top of your root controller which does only one job, listen to tap using Tap gesture. And on tap you can dismiss the view controller and adjust brightness to previous state.
By doing so you dont have to worry about buttons being clicked as they will be below the transparent view controller. Since its a whole new view controller sitting on top of stack you dont have to modify your existing code as well.
Ok I have had a similar problem before.
As I remember I subclassed the UIWindow for full screen detection and made it First responder.
Than I overridden the touch to handle from subclasses.
You can also use code to identify the control that is been touched.
#import <QuartzCore/QuartzCore.h>
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view setMultipleTouchEnabled:YES];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// Enumerate over all the touches
[touches enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
// Get a single touch and it's location
UITouch *touch = obj;
CGPoint touchPoint = [touch locationInView:self.view];
...
}];
}
To disable the locking of screen I used below code:
[[UIApplication sharedApplication] setIdleTimerDisabled:YES];
I used following functions to dim or increase the screen brightness
[[UIScreen mainScreen] setBrightness:0.0f]; //and
[[UIScreen mainScreen] setBrightness:1.0f];

Passing the object from SKScene to UIViewController

I'm trying to pass an object from an SKScene to the current UIViewController where the scene is being shown, it's like I created a label that will only be triggered once the object from the scene reached a specific location, I understand that I can just easily create a SKLabel and have it added to the scene once the object reaches the location I want it to, but I'd rather do it the ViewController style way since I'll be adding a lot of objects that will do the same thing as my app Progress, that reason step aside, I did actually tried adding an sk label to see if it will work that way, Yes I was able to see the SKLabel appear upon the object reaching let's say location.x = 50 and I set the node to be removed when the object reaches location.x = 270, But the problem is it's only doing it once, after the object being added again, it seems that the scene is not removing the node even though I'm pointing my object to hit 270..
By the way, since I mentioned 2 problems, here's the code that executes the said operation for the SKlabel node which is only happening once, I want it to execute the statement one time, everytime I hit that location
if (newLocation.x==270.00 )) {
[self addingTheLabel];
}
if (newLocation.x == 50.00) {
SKAction *removingTheNode = [SKAction removeFromParent];
[self.label runAction:removingTheNode];
}
Nevermind, was able to resolve the issue now..
For those people who might encounter this,Creating a protocol on your scene will fix the issue:
#protocol gameSceneDelegate <NSObject>
-(void)testProtocol;
-(void)testProtocol2;
#end;
#interface MyScene : SKScene
#property (weak,nonatomic) id<gameSceneDelegate> delegate;
Implement it on your scene's view controller:
#interface ViewController : UIViewController<gameSceneDelegate>
in ViewController.m you need to set first your scene as your delegate:
MyScene *scenePointer = (MyScene*) scene;
[scenePointer setDelegate:self];
and finally, implement the methods on your ViewController:
-(void)testProtocol{
NSString *sampleString2 = [[NSString alloc]initWithFormat:#"This will show when testProtocol is selected"];
self.sampleLabel.text = sampleString2;
}
-(void)testProtocol2{
NSString *sampleString3 = [[NSString alloc]initWithFormat:#"This will show when test 2 protocol is selected"];
self.sampleLabel.text = sampleString2;
}
Make an if statement inside your ViewDidLoad that if your scenePoint is the delegate do the following:
if([scenePointer delegate]){
[self testProtocol];
[self testProtocol2];
}
Now, Going to your Scene, since what I want is for the label to change whenever the SpriteNode hits a specific location, what I did is:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
for(UITouch *touch in touches){
CGPoint location = [touch locationInNode:self];
CGPoint newLocation = CGPointMake(location.x, 450);
if (newLocation.x == 270) {
//This will trigger the method everytime the spritenode hit's this location
if([_delegate respondsToSelector:#selector(testProtocol2)]){
[_delegate performSelector:#selector(testProtocol2)];
}
}
if(newLocation.x <= 220){
newLocation.x = 220;
//This will trigger the method everytime the spritenode hit's this location
if([_delegate respondsToSelector:#selector(testProtocol)]){
[_delegate performSelector:#selector(testProtocol)];
}
}
self.spriteNode.position = newLocation;
}
}
It will now change the label every time your SpriteNode hits the location you want it to.
I hope this will help other's who are in the same concept as this.
import your appdelegate and then use
ViewController *vc = (ViewController*)[AppDelegate*)[[UIApplication sharedApplication] delegate] viewController];
to parse the object to your presented UIViewController

How to prevent auto rotation like the iPhone photo app does?

I want to prevent a UICollectionViewController from auto rotation when there's a finger on the screen. The finger could move, the device could rotate, but the UICollectionViewController shouldn't rotate whenever the finger is still on screen.
And the UICollectionViewController should rotate immediately when the finger left the screen. As the iPhone photo app does.
Question:
How to detect touch?
I overwrite the touchBegan:withEvent: etc. in UICollectionView subclass. But when the UICollectionView start scrolling, it calls touchCanceled:withEvent: method.
If I start scrolling the UICollectionView earlier, the touchBegan:withEvent: does even not fired.
How to prevent auto rotation temporarily?
I overwrite the shouldAutorotate in my view controller to prevent rotation. But when the finger left the screen, the UICollectionView can't rotate immediately.
Try this code, please:
#interface BlockAutorotateViewController ()
#property (nonatomic, strong) UILongPressGestureRecognizer *longPressGestureRecognizer;
#property (nonatomic, strong) UISwipeGestureRecognizer *swipeGestureRecognizer;
#property (nonatomic, assign, getter = isPressed) BOOL pressed;
#end
#implementation BlockAutorotateViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.pressed = NO;
self.longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(myLongPressAction:)];
self.longPressGestureRecognizer.minimumPressDuration = 0;
[self.view addGestureRecognizer:self.longPressGestureRecognizer];
self.swipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(mySwipeAction:)];
[self.view addGestureRecognizer:self.swipeGestureRecognizer];
}
- (void)myLongPressAction:(id)sender
{
if ((self.longPressGestureRecognizer.state == UIGestureRecognizerStateEnded) || (self.longPressGestureRecognizer.state == UIGestureRecognizerStateChanged)) {
self.pressed = YES;
}
else if (self.longPressGestureRecognizer.state == UIGestureRecognizerStateBegan) {
self.pressed = NO;
[UIViewController attemptRotationToDeviceOrientation];
}
else {
self.pressed = NO;
}
}
- (void)mySwipeAction:(id)sender
{
if ((self.swipeGestureRecognizer.state == UIGestureRecognizerStateBegan) || (self.longPressGestureRecognizer.state == UIGestureRecognizerStateChanged)) {
self.pressed = YES;
}
else if (self.longPressGestureRecognizer.state == UIGestureRecognizerStateEnded) {
self.pressed = NO;
[UIViewController attemptRotationToDeviceOrientation];
}
else {
self.pressed = NO;
}
}
- (BOOL)shouldAutorotate
{
return (self.isPressed ? NO : YES);
}
#end
For detecting the touch event, I would recommend using a UIGestureRecognizer rather than overriding the UICollectionViewController's touch events. That way you can be notified of the touch event without interfering with the existing event handling.
You will probably need two custom UIGestureRecognizer classes to implement this, since I don't think UITapGestureRecognizer will meet your needs. You can have one UIGestureRecognizer notify you of finger down events and another notify you when the finger is lifted.
For preventing auto rotation, override shouldAutorotate as you have already done. When your gesture recognizer detects that the finger has been lifted, call [UIViewController attemptRotationToDeviceOrientation] to tell iOS to rotate the UI to the current orientation. (Obviously make sure that shouldAutorotate will now return YES before calling that.)

How to cancel button tap if UIGestureRecognizer fires?

Update: The problem seems to be the dependency on another GestureRecognizer to fail. See comments and test project below this question!
In my iPhone app I have a view with multiple UIButtons as subviews. The view also has a UITapGestureRecognizer which is listening for taps with two fingers.
When a two-finger-tap occurs on the view I don't want the buttons to react to the tap, even if one of the fingers was inside the button. I thought this is what "cancelsTouchesInView" is for, but that doesn't work.
My question now is: How to tell my buttons to ignore taps when a gesture is recognized?
Edit: This is my gesture recognizer.
UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(doubleTapped:)];
[doubleTap setNumberOfTouchesRequired:2];
[doubleTap setNumberOfTapsRequired:1];
[doubleTap setCancelsTouchesInView:YES];
[doubleTap setDelaysTouchesBegan:YES];
[doubleTap setDelaysTouchesEnded:YES];
[self.view addGestureRecognizer:doubleTap];
[doubleTap release];
According to an Apple dev this is a bug. I filed a bug report with Apple.
Thanks a lot for your hints, Deepak and gcamp!
Bug report:
Summary:
When adding two UITapGestureRecognizers to a view where one requires the other to fail (requiresGestureRecognizerToFail:) the cancelsTouchesInView property of the first gesture recognizer is ignored.
Steps to Reproduce:
1. Create two UITapGestureRecognizers (r1 and r2)
2. Configure r1 to require two touches and one tap and to delay touchesBegan
3. Configure r2 to require two touches and two taps and to delay touchesBegan
4. Configure r1 to require r2 to fail [r1 requiresGestureRecognizerToFail:r2]
5. Add r1 and r2 to a view
6. Place a UIButton in the view
7. Tap with two fingers on the view, one should hit the button.
Expected Results:
r1 should be recognized and the button tap should be canceled (cancelsTouchesInView defaults to YES for UITapGestureRecognizers).
Actual Results:
r1 is recognized but the button touchedUpInside event is fired, too.
Regression:
cancelTouchesInView works fine for r1 once you remove the dependency on r2 (step 4).
There's a method on UIGestureRecognizer that respond to your question
- (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer
Basically you're requiring that the two tap recognizer fail before accepting the single tap one.
So,
[singleTapRecognizer requireGestureRecognizerToFail:twoTapRecognizer];
Use the delaysTouchesBegan property. Set it to YES.
Alternative
Disable user interaction on the buttons and attach a single finger tap recognizer like mentioned. In the tap handler, check if the tap falls within the bounds of the button. If it is within the bounds of a button, do [theButton sendActionsForControlEvents:UIControlEventTouchUpInside];. This will trigger the touch up event as desired even though the user interaction is disabled.
Mark, I ran into the same bug. If you'll post your radar #, I'll dupe it in bugreporter.
I wrote the following workaround. I subclass UITapGestureRecognizer, then use the subclass to track the touches which triggered the gesture action. If we get the same touches in touchesEnded on the view, we redirect to touchesCancelled. 'immediateTarget' is needed because the touchesEnded call on the view comes after the gesture's touchesEnded but before the gesture's action is called.
RPTapGestureRecognizer.h:
#import <UIKit/UIKit.h>
#import <UIKit/UIGestureRecognizerSubclass.h>
#interface RPTapGestureRecognizer : UITapGestureRecognizer
#property (nonatomic, retain) NSSet *lastTouches;
- (void)setImmediateTarget:(id)inTarget action:(SEL)inAction;
#end
RPTapGestureRecognizer.m:
#import "RPTapGestureRecognizer.h"
#interface RPTapGestureRecognizer ()
#property (nonatomic) SEL immediateAction;
#property (nonatomic, assign) id immediateTarget;
#end
#implementation RPTapGestureRecognizer
#synthesize lastTouches;
#synthesize immediateAction, immediateTarget;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
self.lastTouches = nil;
[super touchesBegan:touches withEvent:event];
}
- (void)setImmediateTarget:(id)inTarget action:(SEL)inAction
{
self.immediateTarget = inTarget;
self.immediateAction = inAction;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UIGestureRecognizerState startingState, finalState;
startingState = self.state;
[super touchesEnded:touches withEvent:event];
finalState = self.state;
if (startingState != finalState &&
finalState == UIGestureRecognizerStateEnded) {
/* Must copy; the underlying NSCFSet will be modified by the superclass, removing the touches */
self.lastTouches = [[touches copy] autorelease];
if (self.immediateAction)
[self.immediateTarget performSelector:self.immediateAction
withObject:self];
}
}
- (void)dealloc
{
self.lastTouches = nil;
self.immediateTarget = nil;
[super dealloc];
}
#end
In the view:
#synthesize lastTapGesture
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if (self.lastTapGesture && [self.lastTapGesture.lastTouches isEqualToSet:touches]) {
[self touchesCancelled:touches withEvent:event];
self.lastTapGesture = nil;
return;
}
/* touches ended implementation here */
}
- (void)willHandleMouseTapGesture:(RPTapGestureRecognizer *)tapGesture
{
/* Because one tap gesture is dependent upon another, the touchesEnded method is still going to be called, instead of touchesCanceled.
* This is an Apple bug against all versions of iOS at least up to 5.0. It will be called in the run loop before tapGesture's action is called.
*
* See http://stackoverflow.com/questions/6188997/how-to-cancel-button-tap-if-uigesturerecognizer-fires/6211922#6211922 for details.
*/
self.lastTapGesture = tapGesture;
}
- (void)configureGestures
{
/* Two finger taps */
RPTapGestureRecognizer *tapGestureOne;
tapGestureOne = [[[RPTapGestureRecognizer alloc] initWithTarget:self
action:#selector(handleMouseTapGesture:)] autorelease];
tapGestureOne.numberOfTapsRequired = 1;
tapGestureOne.numberOfTouchesRequired = 2;
[tapGestureOne setImmediateTarget:self action:#selector(willHandleMouseTapGesture:)];
[self addGestureRecognizer:tapGestureOne];
[myGestures addObject:tapGestureOne];
RPTapGestureRecognizer *tapGestureTwo;
tapGestureTwo = [[[RPTapGestureRecognizer alloc] initWithTarget:self
action:#selector(handleMouseTapGesture:)] autorelease];
tapGestureTwo.numberOfTapsRequired = 2;
tapGestureTwo.numberOfTouchesRequired = 2;
[tapGestureTwo setImmediateTarget:self action:#selector(willHandleMouseTapGesture:)];
[self addGestureRecognizer:tapGestureTwo];
}

Resources