How can I supersede the built-in swipe gesture recognizer in UICollectionView? - iphone

We're building an app that takes advantage of the new UICollectionView in iOS 6. However, we need to implement a long-press behavior such that even if the user then moves their finger after, we want it ignored.
i.e.
User touches the screen than instantly moves -> Swipe
User touches the screen, pauses, then moves -> Ignore swipe and wait for the release.
Basically, we only want to allow the built-in swipe gesture to be enabled if our gesture recognizer fails. However, we're not sure how to supersede the built-in swipe gesture recognizers with that 'Other recognizer must fail first' logic.
We're not sure if we're allowed to simply walk the chain trying to find the UIScrollView and interrogate that as we don't know if that violates Apple's guidelines, and if I remember correctly, they actually warn against messing with their recognizers anyway.
So how can we create tap-to-hold recognizers that supersede the built in ones?

There's an example on page 36 of the UICollectionView programming guide:
UITapGestureRecognizer* tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapGesture:)];
NSArray* recognizers = [self.collectionView gestureRecognizers];
// Make the default gesture recognizer wait until the custom one fails.
for (UIGestureRecognizer* aRecognizer in recognizers) {
if ([aRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
[aRecognizer requireGestureRecognizerToFail:tapGesture];
}
}
// Now add the gesture recognizer to the collection view.
tapGesture.numberOfTapsRequired = 2;
[self.collectionView addGestureRecognizer:tapGesture];
Original answer
Have a look at UITapGestureRecognizerDelegate, which can be used to allow two gesture recognizers to process touches at once:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
For more info, see a tutorial such as this:
http://www.raywenderlich.com/6567/uigesturerecognizer-tutorial-in-ios-5-pinches-pans-and-more

Before your new UILongPressGestureRecognizer transnitions from the possible state, he will ask its delegate gestureRecognizerShouldBegin:. You can use that delegate method to cancel (force to failed state) any other gesture recogniser attached to the same view.
You do this by implementing the following as a delegate for your new UILongPressGestureRecognizer:
#implementation DragPictogramGestureRecognizerDelegate
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
for (UIGestureRecognizer *gr in gestureRecognizer.view.gestureRecognizers) {
if ([gr isKindOfClass:[UILongPressGestureRecognizer class]] == NO) {
gr.enabled = NO;
gr.enabled = YES;
}
}
return YES;
}
#end
Further more, in order to allow the user to use your new UILongPressGestureRecognizer with one finger, and use another finger to scroll the UICollectionView at the same time, you can implement the following delegate in the same class.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}

Related

Manipulating Gesture Recognizers in iOS 7

I have a gesture recognizer that I've made on a calculator. It's connected to an action that is activated upon a user's double tap. I connected the gesture recognizer to the main view of the view controller, however the gesture recognizer is also applied to my buttons. So if the user quickly types 11, they'll press 1 two times fast and accidentally activate a function that they don't want to. How do I make it so that the UIGestureRecognizer doesn't act upon buttons? I want to keep the double-tap gesture. I don't want to change it to a two finger tap or something odd like that. However, if there's no other way (which I doubt there is), I could do that.
Try this:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if ((touch.view == yourButton)) {
return NO;
}
return YES;
}
This will be called every time the gesture is recognized, and it will ignore the gesture is the view is your button.
You should adopt UIGestureRegconizerDelegate protocol in your ViewController.h
#interface ViewController : UIViewController <UIGestureRecognizerDelegate>
#end
in your ViewController.m, implement this method
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if ([touch.view isKindOfClass:[UIControl class]]){
return NO;
}
return YES;
}
and yourGestureRecognizer.delegate = self //your view controller.
Building on Antonio's answer, you could give all of your tags to make this easier. Presumably, your calculator has more than just one yourButton. Suppose all your buttons have a tag larger than 100:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldReceiveTouch:(UITouch *)touch {
if ((touch.view.tag > 100)) {
return NO;
}
return YES;
}
This is cleaner than checking the class of the view. You could now have controls where you allow the gesture to be recognised anyway.

Why is UIPanGestureRecognizer not working at the top portion of the screen in iOS 7?

I created a UIPanGestureRecognizer in a UICollectionViewCell subclass. It works in all cells except the top of the screen. About the height of status bar the pan gesture recognizer is dead. It doesn't fire. My test app only has a UICollectionView and nothing else. No status bar. I think maybe it has to do with the notification center "active edge" stealing touch events.
_panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panGestureRecognized:)];
_panGestureRecognizer.delegate = self;
_panGestureRecognizer.maximumNumberOfTouches = 1;
[self addGestureRecognizer:_panGestureRecognizer];
At the top of screen when I pan in the cell I get no message upon pan begin. There is no call to the callback until I lift the finger. Then the callback receives BEGAN and immediately after that it receives ENDED.
- (void)panGestureRecognized:(UIPanGestureRecognizer*)sender {
NSLog(#"pan detected"); // for first cell, only called when finger is lifted up.
if (sender.state == UIGestureRecognizerStateBegan) {
NSLog(#"BEGAN");
} else if (sender.state == UIGestureRecognizerStateEnded) {
NSLog(#"ENDED");
}
}
The cell is the delegate of the gesture recognizer. It implements:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
The cell also overwrites the UIView method:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
return YES;
}
Is this a known problem with pan gesture recognizers at the top of the screen? And is there a workaround?

How to create Tap event on Long Tap gesture on UIWebView?

I have added Long Tap gesture on UIWebView. But I want UIWebView to process a standard Tap event before my Long Tap will be recognized. (Two gestures should be processed on Long Tap - a simple Tap and my Long Tap). How to do this?
I think it's required to send Tap event to UIWebView on TouchBegin. Is it correct?
The correct code:
- (void)viewDidLoad {
UILongPressGestureRecognizer* gesture = [[[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleLongPress:)] autorelease];
gesture.delegate = self;
[myWebView addGestureRecognizer:gesture];
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
Please refer this section UIGestureRecognizerDelegate
http://developer.apple.com/library/ios/#documentation/uikit/reference/UIGestureRecognizerDelegate_Protocol/Reference/Reference.html
you found this is called when 2 gesture simultaneous work.
gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:

UIGestureRecognizerDelegate never shows tap as in state 'recognized'

Im slightly confused by a method in the UIGestureRecognizerDelegate protocol. When I implement the delegate method below, I don't seem to ever get my UITapGestureRecognizers sent to this method with their state as UIGestureRecognizerStateRecognized. They are always in the state UIGestureRecognizerStatePossible. Is this right?
Below is the test code I used to setup my Tap Gesture and my test implementation of the delegate method:
UITapGestureRecognizer *singelTap = [[UITapGestureRecognizer alloc] initWithTarget:nil action:nil];
singelTap.numberOfTapsRequired = 1;
singelTap.delegate = self;
[self.view addGestureRecognizer:singelTap];
.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
int i = 0;
if(gestureRecognizer.state == UIGestureRecognizerStateBegan){
i=1;
}
if(gestureRecognizer.state == ..... //testing for all possible states...
return YES;
}
This is correct. When you implement the
(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
method, you get an opportunity to decide if the UIGestureRecognizer should receive the touch. That means that it has not received the touch yet. Because the UIGestureRecognizer in question has not received anything to do with that particular touch yet, it hasn't been able to determine if it Recognized it or not; thus it remains in the state UIGestureRecognizerStatePossible during this method.
If you return YES from the method, the touch will be sent to the UIGestureRecognizer.
Only then will the UIGestureRecognizer handle the touch.
The UIGestureRecognizerDelegate protocol is only to fine-tune the gesture recognizer's behavior. The actual recognition of events is done by setting a target and a selector:
UITapGestureRecognizer *gr = [[[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(handleTap:)] autorelease];
// add gr to a view
-(void)handleTap:(UITapGestureRecognizer*)tg
{
if ( tg.state != UIGestureRecognizerStateRecognized ) return;
NSLog(#"tap recognized");
}
In most cases, you can safely ignore the UIGestureRecognizerDelegate protocol.
Found this in the documents
This method is called before touchesBegan:withEvent: is called on the gesture recognizer for a new touch.
So I guess the touch can only ever be in the 'possible' state.

UITapGestureRecognizer not doing anything

I've got an app that displays a page of text with the ability to tap a button or swipe in a view to advance or retreat through various pages. The container view has two UISwipeGestureRecognizers attached, for swipe left and swipe right. No trouble with these gestures. But now I'm trying to add UITapGestureRecognizer to another view, providing an ability similar to iBooks or the Kindle app, tap the left to go back, the right to go forward. Nothing I do can get the gesture to fire. No hint that it is ever being triggered, even if I put the gesture on my topmost view and disable other gestures.
The view controller implements UIGestureRecognizerDelegate, though I've not needed to implement delegate methods. I did try implementing shouldReceiveTouch: without success.
Here's a snippet of code where I create and attach the gesture recognizer:
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapGestureTurnPage:)];
[self.view addGestureRecognizer:recognizer];
[recognizer release];
Try this on the view you're adding the gesture recognizers:
view.userInteractionEnabled = YES;
You mention that you've tried this, but just in case go through it again.
Try using the delegate with <UIGestureRecognizerDelegate> in the header and then setting the delegate:
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapGestureTurnPage:)];
[self.view addGestureRecognizer:recognizer];
recognizer.delegate = self;
[recognizer release];
Then implement this method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldReceiveTouch:(UITouch *)touch {
return YES;
}
Then use a debugger or toss an NSLog into the above method and also tapGestureTurnPage: to see if these methods are being called.
Add this where you initialize the gesture:
recognizer.delegate = self;
and add this in the "self" class:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
This allows me to have gestures recognized on the UIWebView, and the UIWebView will still respond to the taps. (which i wanted - you may not)