I have a subview inside a view. That view has a PanGestureRecognizer.
I want user could zoom in inside the webview, pan around, zoom out, etc.
But I want that, when the user reaches left edge of the webview, the gesturesrecognizers inside the webView to be ignored, and only the PanGestureRecognizer from the view get called.
Well, I have accomplished this, by that code:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
float coordY = scrollView.contentOffset.y;
float correctionOffsetRightSide = scrollView.contentSize.width-webView.bounds.size.width;
if(scrollView.contentOffset.x <= 0)
{
[delegate viewOnlyRecognizeItsPan];
[scrollView setContentOffset:CGPointMake(0, coordY)];
}
else if (scrollView.contentOffset.x >= correctionOffsetRightSide)
{
[delegate viewOnlyRecognizeItsPan];
[scrollView setContentOffset:CGPointMake(correctionOffsetRightSide, coordY)];
}
}
-(void)viewOnlyRecognizeItsPan
{
for (UIGestureRecognizer *gesture in [[[[arrayDeAbas objectAtIndex:currentViewTag] webView] scrollView] gestureRecognizers])
{
[gesture requireGestureRecognizerToFail:panRecognizer];
}
}
"arrayDeAbas" is an mutableArray that stores webviews.
My problem is: after user touchs a button, I need to reset "gestures", so that it doesn't requires panRecognizer to fail anymore.
How could I could this?
EDITED:
Ok, I've founded a solution. Instead of using requireGestureRecognizerToFail:, I can simply setEnablle to YES or NO.
-(void)turnPanON
{
for (UIGestureRecognizer *gesture in [[[[arrayDeAbas objectAtIndex:currentViewTag] webView] scrollView] gestureRecognizers])
{
gesture.enabled = NO;
}
}
-(void)turnPanOFF;
{
for (UIGestureRecognizer *gesture in [[[[arrayDeAbas objectAtIndex:currentViewTag] webView] scrollView] gestureRecognizers])
{
gesture.enabled = YES;
}
}
I am trying to make some polygons (MKOverlayViews) tappable by adding a gesture recognizer to each of them.
I have an NSArray containing KMLParsers (from the KMLViewer sample code).
-(MKOverlayView*)mapView:(MKMapView *)mapView viewForOverlay:(id<MKOverlay>)overlay{
for (NSString *key in kmls) {
KMLParser *kml2=[kmls objectForKey:key];
MKOverlayView *oView;
if ( (oView = [kml2 viewForOverlay:overlay]) ) {
if (layerNumber == 8) {
NSLog(#"8");
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]
initWithTarget:self
action:#selector(showZoneText:)];
tap.numberOfTapsRequired = 1;
tap.numberOfTouchesRequired = 1;
[oView addGestureRecognizer:tap];
[tap release];
}
return oView;
}
}
return nil;
}
This code logs 8, therefore the "if" block is executed, and the layer number 8 is displayed.
However, the "showZoneText" method is never fired when I tap the polygons (an NSLog in that method never shows in the console).
-(void)showZoneText:(UITapGestureRecognizer*)recognizer{
NSLog(#"show");
}
Any help on this?
thanks,
G.
I need to recognize swipes in all directions (Up/Down/Left/Right). Not simultaneously, but I need to recognize them.
I tried:
UISwipeGestureRecognizer *Swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(SwipeRecognizer:)];
Swipe.direction = (UISwipeGestureRecognizerDirectionLeft |
UISwipeGestureRecognizerDirectionRight |
UISwipeGestureRecognizerDirectionDown |
UISwipeGestureRecognizerDirectionUp);
[self.view addGestureRecognizer:Swipe];
[Swipe release];
but nothing appeared on SwipeRecognizer
Here's the code for SwipeRecognizer:
- (void) SwipeRecognizer:(UISwipeGestureRecognizer *)sender {
if ( sender.direction == UISwipeGestureRecognizerDirectionLeft )
NSLog(#" *** SWIPE LEFT ***");
if ( sender.direction == UISwipeGestureRecognizerDirectionRight )
NSLog(#" *** SWIPE RIGHT ***");
if ( sender.direction == UISwipeGestureRecognizerDirectionDown )
NSLog(#" *** SWIPE DOWN ***");
if ( sender.direction == UISwipeGestureRecognizerDirectionUp )
NSLog(#" *** SWIPE UP ***");
}
How can I do this? How can assign to my Swipe object all different directions?
You set the direction like this
UISwipeGestureRecognizer *Swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(SwipeRecognizer:)];
Swipe.direction = (UISwipeGestureRecognizerDirectionLeft |
UISwipeGestureRecognizerDirectionRight |
UISwipeGestureRecognizerDirectionDown |
UISwipeGestureRecognizerDirectionUp);
That's what the direction will be when you get the callback, so it is normal that all your tests fails. If you had
- (void) SwipeRecognizer:(UISwipeGestureRecognizer *)sender {
if ( sender.direction | UISwipeGestureRecognizerDirectionLeft )
NSLog(#" *** SWIPE LEFT ***");
if ( sender.direction | UISwipeGestureRecognizerDirectionRight )
NSLog(#" *** SWIPE RIGHT ***");
if ( sender.direction | UISwipeGestureRecognizerDirectionDown )
NSLog(#" *** SWIPE DOWN ***");
if ( sender.direction | UISwipeGestureRecognizerDirectionUp )
NSLog(#" *** SWIPE UP ***");
}
The tests would succeed (but the would all succeed so you wouldn't get any information out of them). If you want to distinguish between swipes in different directions you will need separate gesture recognizers.
EDIT
As pointed out in the comments, see this answer. Apparently even this doesn't work. You should create swipe with only one direction to make your life easier.
Unfortunately you cannot use direction property for listening the recognizer; it only gives you the detected directions by the recognizer. I have used two different UISwipeGestureRecognizers for that purpose, see my answer here: https://stackoverflow.com/a/16810160/936957
I actually ran into this exact problem before. What I ended up doing was creating a UIView subclass, overriding touchesMoved: and doing some math to calculate the direction.
Here's the general idea:
#import "OmnidirectionalControl.h"
typedef NS_ENUM(NSInteger, direction) {
Down = 0, DownRight = 1,
Right = 2, UpRight = 3,
Up = 4, UpLeft = 5,
Left = 6, DownLeft = 7
};
#interface OmnidirectionalControl ()
#property (nonatomic) CGPoint startTouch;
#property (nonatomic) CGPoint endTouch;
#end
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.startTouch = [[touches allObjects][0] locationInView:self];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
self.lastTouch = [[[touches allObjects] lastObject] locationInView:self];
NSLog(#"Direction: %d", [self calculateDirectionFromTouches]);
}
-(direction)calculateDirectionFromTouches {
NSInteger xDisplacement = self.lastTouch.x-self.startTouch.x;
NSInteger yDisplacement = self.lastTouch.y-self.startTouch.y;
float angle = atan2(xDisplacement, yDisplacement);
int octant = (int)(round(8 * angle / (2 * M_PI) + 8)) % 8;
return (direction) octant;
}
For simplicity's sake in touchesMoved:, I only log the direction, but you can do what you want with that information (e.g. pass it to a delegate, post a notification with it, etc.). In touchesMoved you'll also need some method to recognize if the swipe is finished or not, but that's not quite relevant to the question so I'll leave that to you. The math in calculateDirectionFromTouches is explained here.
I finally found the simplest answer, please mark this as the answer if you agree.
If you only have one direction swipe + pan, you just say:
[myPanRecogznier requireGestureRecognizerToFail:mySwipeRecognizer];
But if you have two or more swipes, you can't pass an array into that method. For that, there's UIGestureRecognizerDelegate that you need to implement.
For example, if you want to recognize 2 swipes (left and right) and you also want to allow the user to pan up, you define the gesture recognizers as properties or instance variables, and then you set your VC as the delegate on the pan gesture recognizer:
_swipeLeft = [[UISwipeGestureRecognizer alloc] ...]; // use proper init here
_swipeRight = [[UISwipeGestureRecognizer alloc] ...]; // user proper init here
_swipeLeft.direction = UISwipeGestureRecognizerDirectionLeft;
_swipeRight.direction = UISwipeGestureRecognizerDirectionRight;
_pan = [[UIPanGestureRecognizer alloc] ...]; // use proper init here
_pan.delegate = self;
// then add recognizers to your view
You then implement - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer delegate method, like so:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if (gestureRecognizer == _pan && (otherGestureRecognizer == _swipeLeft || otherGestureRecognizer == _swipeRight)) {
return YES;
}
return NO;
}
This tells the pan gesture recognizer to only work if both left and right swipes fail to be recognize - perfect!
Hopefully in the future Apple will just let us pass an array to the requireGestureRecognizerToFail: method.
Use a UIPanGestureRecogizer and detect the swipe directions you care about. see the UIPanGestureRecognizer documentation for details. -rrh
// add pan recognizer to the view when initialized
UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panRecognized:)];
[panRecognizer setDelegate:self];
[self addGestureRecognizer:panRecognizer]; // add to the view you want to detect swipe on
-(void)panRecognized:(UIPanGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateBegan) {
// you might want to do something at the start of the pan
}
CGPoint distance = [sender translationInView:self]; // get distance of pan/swipe in the view in which the gesture recognizer was added
CGPoint velocity = [sender velocityInView:self]; // get velocity of pan/swipe in the view in which the gesture recognizer was added
float usersSwipeSpeed = abs(velocity.x); // use this if you need to move an object at a speed that matches the users swipe speed
NSLog(#"swipe speed:%f", usersSwipeSpeed);
if (sender.state == UIGestureRecognizerStateEnded) {
[sender cancelsTouchesInView]; // you may or may not need this - check documentation if unsure
if (distance.x > 0) { // right
NSLog(#"user swiped right");
} else if (distance.x < 0) { //left
NSLog(#"user swiped left");
}
if (distance.y > 0) { // down
NSLog(#"user swiped down");
} else if (distance.y < 0) { //up
NSLog(#"user swiped up");
}
// Note: if you don't want both axis directions to be triggered (i.e. up and right) you can add a tolerence instead of checking the distance against 0 you could check for greater and less than 50 or 100, etc.
}
}
CHANHE IN STATEEND CODE WIH THIS
//if YOU WANT ONLY SINGLE SWIPE FROM UP,DOWN,LEFT AND RIGHT
if (sender.state == UIGestureRecognizerStateEnded) {
[sender cancelsTouchesInView]; // you may or may not need this - check documentation if unsure
if (distance.x > 0 && abs(distance.x)>abs(distance.y)) { // right
NSLog(#"user swiped right");
} else if (distance.x < 0 && abs(distance.x)>abs(distance.y)) { //left
NSLog(#"user swiped left");
}
if (distance.y > 0 && abs(distance.y)>abs(distance.x)) { // down
NSLog(#"user swiped down");
} else if (distance.y < 0 && abs(distance.y)>abs(distance.x)) { //up
NSLog(#"user swiped up");
}
}
You can add only one diagonal by SwipeGesture, then...
UISwipeGestureRecognizer *swipeV = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(action)];
UISwipeGestureRecognizer *swipeH = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(action)];
swipeH.direction = ( UISwipeGestureRecognizerDirectionLeft | UISwipeGestureRecognizerDirectionRight );
swipeV.direction = ( UISwipeGestureRecognizerDirectionUp | UISwipeGestureRecognizerDirectionDown );
[self addGestureRecognizer:swipeH];
[self addGestureRecognizer:swipeV];
self.userInteractionEnabled = YES;
UISwipeGestureRecognizer *Updown=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(handleGestureNext:)];
Updown.delegate=self;
[Updown setDirection:UISwipeGestureRecognizerDirectionDown | UISwipeGestureRecognizerDirectionUp];
[overLayView addGestureRecognizer:Updown];
UISwipeGestureRecognizer *LeftRight=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(handleGestureNext:)];
LeftRight.delegate=self;
[LeftRight setDirection:UISwipeGestureRecognizerDirectionLeft | UISwipeGestureRecognizerDirectionRight];
[overLayView addGestureRecognizer:LeftRight];
overLayView.userInteractionEnabled=NO;
-(void)handleGestureNext:(UISwipeGestureRecognizer *)recognizer
{
NSLog(#"Swipe Recevied");
}
It might not be the best solution but you can always specify different UISwipeGestureRecognizer for each swipe direction you want to detect.
In the viewDidLoad method just define the required UISwipeGestureRecognizer:
- (void)viewDidLoad{
UISwipeGestureRecognizer *recognizerUp = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipeUp:)];
recognizerUp.direction = UISwipeGestureRecognizerDirectionUp;
[[self view] addGestureRecognizer:recognizerUp];
UISwipeGestureRecognizer *recognizerDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipeDown:)];
recognizerDown.direction = UISwipeGestureRecognizerDirectionDown;
[[self view] addGestureRecognizer:recognizerDown];
}
Then just implement the respective methods to handle the swipes:
- (void)handleSwipeUp:(UISwipeGestureRecognizer *)sender{
if (sender.state == UIGestureRecognizerStateEnded){
NSLog(#"SWIPE UP");
}
}
- (void)handleSwipeDown:(UISwipeGestureRecognizer *)sender{
if (sender.state == UIGestureRecognizerStateEnded){
NSLog(#"SWIPE DOWN");
}
}
i am trying to implement my own gesture recognizer in addition to the one already used by the MKMapView. Right now i can tap on the map and set a pin. This behavior is realized by my UITapGestureRecognizer. When i tap on a pin that already exists, my gesture recognizer does nothing, but instead the callout bubble of this pin is shown. The UIGestureRecognizerDelegate looks like this:
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if (gestureRecognizer == self.tapRecognizer)
{
bool hitAnnotation = false;
int count = [self.mapView.annotations count];
int counter = 0;
while (counter < count && hitAnnotation == false )
{
if (touch.view == [self.mapView viewForAnnotation:[self.mapView.annotations objectAtIndex:counter]])
{
hitAnnotation = true;
}
counter++;
}
if (hitAnnotation)
{
return NO;
}
}
return YES;
}
This works fine. My only problem are the callout bubbles of the pins and the double tap. Normally the double tap is used for zooming in. This still works but in addition to this, i also get a new pin. Is there any way to avoid this?
The other problem occurs with the callout bubble of a pin. I can open the bubble by tapping on the pin without setting a new pin at this place (see code above) but when i want to close the bubble by tapping on it, another pin is set. My problem is, that i cannot check with touch.view , if the user tapped on a callout bubble, because it is not a regular UIView as far as i know. Any ideas or workarounds for this problem?
Thanks
I had the same problem as your first problem: distinguishing double taps from single taps in an MKMapView. What I did was the following:
[doubleTapper release];
doubleTapper = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(mapDoubleTapped:)];
doubleTapper.numberOfTapsRequired = 2;
doubleTapper.delaysTouchesBegan = NO;
doubleTapper.delaysTouchesEnded = NO;
doubleTapper.cancelsTouchesInView = NO;
doubleTapper.delegate = self;
[mapTapper release];
mapTapper = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(mapTapped:)];
mapTapper.numberOfTapsRequired = 1;
mapTapper.delaysTouchesBegan = NO;
mapTapper.delaysTouchesEnded = NO;
mapTapper.cancelsTouchesInView = NO;
[mapTapper requireGestureRecognizerToFail:doubleTapper];
and then implemented the following delegate method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
Using requireGestureRecognizerToFail: allows the app to distinguish single taps from double taps and implementing gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: ensures that double taps are still forwarded to the MKMapView so that it continues zooming normally. Note that doubleTapper doesn't actually do anything (in my case, except log debug messages). It's simply a dummy UIGestureRecognizer that's used to help separate single taps from double taps.
Has anyone managed to get the gesture recognition working in cocos-2d?
I have read a post here that claimed to have achieved it, here: http://www.cocos2d-iphone.org/forum/topic/8929
I patched from the git hub here: https://github.com/xemus/cocos2d-GestureRecognizers/blob/master/README
I made a subclass of CCSprite (which is a subclass of CCNode):
-(id) initWithTexture:(CCTexture2D*)texture rect:(CGRect)rect {
if( (self=[super initWithTexture:texture rect:rect]) )
{
CCGestureRecognizer* recognizer;
recognizer = [CCGestureRecognizer
CCRecognizerWithRecognizerTargetAction:[[[UITapGestureRecognizer alloc]init] autorelease]
target:self
action:#selector(tap:node:)];
[self addGestureRecognizer:recognizer];
}
return self;
}
Delegate method:
- (void) swipe:(UIGestureRecognizer*)recognizer node:(CCNode*)node
{
NSLog(#" I never get called :( ");
}
My tap event never gets called.
Has anyone got this working? How difficult is it to do gesture recognition manually for swipe detection?
You need to attach the gesture recognizer to something "up the chain". Don't attach them to the individual nodes; attach them to the UIView (i.e., [[CCDirector sharedDirector] openGLView]).
Here's what I did:
- (UIPanGestureRecognizer *)watchForPan:(SEL)selector number:(int)tapsRequired {
UIPanGestureRecognizer *recognizer = [[[UIPanGestureRecognizer alloc] initWithTarget:self action:selector] autorelease];
recognizer.minimumNumberOfTouches = tapsRequired;
[[[CCDirector sharedDirector] openGLView] addGestureRecognizer:recognizer];
return recognizer;
}
- (void)unwatch:(UIGestureRecognizer *)gr {
[[[CCDirector sharedDirector] openGLView] removeGestureRecognizer:gr];
}
This particular code is used in a superclass for scene controllers, so the target for the selector is hard-coded to "self", but you could easily abstract that to a passed-in object. Also, you could extrapolate the above to easily create gesture recognizers for taps, pinches, etc.
In the subclass for the controller, then, I just do this:
- (MyController *)init {
if ((self = [super init])) {
[self watchForPan:#selector(panning:) number:1];
}
return self;
}
- (void)panning:(UIPanGestureRecognizer *)recognizer {
CGPoint p;
CGPoint v;
switch( recognizer.state ) {
case UIGestureRecognizerStatePossible:
case UIGestureRecognizerStateBegan:
p = [recognizer locationInView:[CCDirector sharedDirector].openGLView];
(do something when the pan begins)
break;
case UIGestureRecognizerStateChanged:
p = [recognizer locationInView:[CCDirector sharedDirector].openGLView];
(do something while the pan is in progress)
break;
case UIGestureRecognizerStateFailed:
break;
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled:
(do something when the pan ends)
(the below gets the velocity; good for letting player "fling" things)
v = [recognizer velocityInView:[CCDirector sharedDirector].openGLView];
break;
}
}
If you don't want to handle everything manually I created a simple category that will add gesture recognizers to any cocos2d version
read more at:
http://www.merowing.info/2012/03/using-gesturerecognizers-in-cocos2d/
or grab it from github
https://github.com/krzysztofzablocki/CCNode-SFGestureRecognizers