In IOS 6, how to add PinchGesture that only detect once?
I have a UIView that I add to pinchGesture:
[self addPinchGestureRecognizersToView:self.view];
Then I attach a function to this Pinch to call out a uiview. The problem is when I pinch, the event occur a few times, that make the ViewController to addSubview many times depend on how many times the event occur.
So how can I actually limit it to just 1 time or remove it at the moment it detect a pinch. I tried:
[self.view removeGestureRecognizer:UIPinchGestureRecognizer];
But I got an compile error.
Thanks for all the suggestions. I just thought of the simplest solution - Add a BOOLEAN to check. The rest work like a charm.
You should know pinch gesture is a continuous gesture. That is to say it can be recognized many time during the touch procedure.
If you want to recognize only once, you can remove it the first time it recognizes. The reason you get a compile error is you should 'remember' your gesture and remove it later.
[self.view removeGestureRecognizer:UIPinchGestureRecognizer];
This method call is invalid. UIPinchGestureRecognizer is a class not an instance. You have to replace it with the correct recognizer you have added.
for (UIGestureRecognizer* recognizer in [self.view.gestureRecognizers copy]) {
if ([recognizer isKindOfClass:[UIPinchGestureRecognizer class]]) {
[self.view removeGestureRecognizer:recognizer];
}
}
Related
I'm implementing the Messages app copy-message feature.
You can either double tap or long press on a message to copy it.
How do I do that?
I was thinking of adding two gesture recognizers to the view, one UITapGestureRecognizer (with numberOfTapsRequired set to 2) and one UILongPressGestureRecognizer. They would both have the same target & action.
Then, I think for each of them, I'd call requireGestureRecognizerToFail:, passing the other gesture recognizer.
Is my thinking correct? Is there anything I'm missing, or is there a better way to do this?
Simply add the gestures to your view (easy to do programatically) and set the selector to the desired method. However, you're probably going to get some push back since you don't provide any code or hint that you have tried to solve your problem before coming here. I'm new here as well, but have seen some questions put on hold for those reasons.
As you said that for double tap and long press on a message to copy. So both of them are using for same action. So I think you can do it on same method.
You can try this method in UIGestureRecognizerDelegate
gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:
refer this for more detail:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIGestureRecognizerDelegate_Protocol/Reference/Reference.html#//apple_ref/occ/intfm/UIGestureRecognizerDelegate/gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:
this helps recognize more than one gesture recognizer at a time.
Yes, as you say, create two gesture recognizers (one long-press and one double-tap) and add them both to the same view.
Don't call requireGestureRecognizerToFail: on either of them because long-press & double-tap gestures play nice together by default.
You can give them both the same target and action, but each gesture requires different logic to determine whether you should show the copy menu.
- (void)messageCopyMenuShowAction:(UILongPressGestureRecognizer *)gestureRecognizer
{
BOOL doubleTap = (gestureRecognizer.numberOfTapsRequired == 2);
if ((doubleTap && gestureRecognizer.state == UIGestureRecognizerStateEnded) || // double-tap
(!doubleTap && gestureRecognizer.state == UIGestureRecognizerStateBegan)) { // long-press
// Show copy menu.
}
}
I'm getting intermittent reports from users on iOS 7 saying that the UIPanGestureRecognizer stops working on certain views every once in a while. They're supposed to be able to swipe a view to the right/left, but it just breaks and doesn't work for some unknown reason. Force quitting the app and relaunching it fixes the problem.
This problem never happened on iOS 6. And I don't have any code that disables the gesture recognizer at any time besides the gestureRecognizerShouldBegin delegate that forces the gesture to only recognize horizontal pans:
- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer {
if ([gestureRecognizer isMemberOfClass:[UIPanGestureRecognizer class]]) {
CGPoint translation = [gestureRecognizer translationInView:[self superview]];
if (fabsf(translation.x) > fabsf(translation.y)) {
if (translation.x > 0)
return YES;
}
}
return NO;
}
Did anything change in the UIPanGestureRecognizer (or just the plain UIGestureRecognizer) that could be causing this problem?
I think I finally solved this issue. Apparently iOS 7 handles gestures in subviews differently than it did in iOS 6 and earlier. To handle this, Apple implemented a new delegate:
(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
If you return YES, that should get your gesture recognizer to work. I've implemented it and haven't had any issues so far (though admittedly this was a rare bug that I could never reliably reproduce, so it's possible that it just hasn't recurred yet).
For more information, see
https://stackoverflow.com/a/19892166/1593765.
Why would you return NO in gesture recognizer just because on gestureRecognizerShouldBegin: the movement is only vertical?
Since it's gesture made by user with his finger (and not made by a machine), there will always be some randomness in it's movement due to inaccuracy of moving finger. gestureRecognizerShouldBegin: will be called just after user touches the screen and the translation you get might be just a few pixels. Your recognizer will fail if user i.e. when putting his finger on screen moves it 2 pixels up, even if he then moves it 200 pixels to the right.
This shouldn't cause the gesture recognizer to be permanently disabled but you should look into it as well, since it might confuse users when their gestures are not recognized for seemingly no reason.
My table view has a text field above it. And whenever the text field is focussed, a swipe gesture is registered. When the swipe gesture is recognized, the keyboard is dismissed. The code is working for all gestures except for swipe up gesture is not working. This is my code
swipe = [[UISwipeGestureRecognizer alloc]
initWithTarget:self action:#selector(dismissKeyboard)];
[swipe setDirection:UISwipeGestureRecognizerDirectionUp];
Can someone please let me know if there is any problem?
if all the other gestures works, that means there is no logic problem.
check out spelling errors.
and reapply the swipe gesture, and check out everything (outlets etc.).
I don't know about this case, but I know that when I've had gestures on a custom container view and then added a child view with its own gestures, I've had to iterate through the child's gestures and tell them to require my gestures to fail (i.e. mine take precedence). I've done this with scroll views successfully:
for (UIGestureRecognizer *gesture in self.scrollView.gestureRecognizers)
{
[gesture requireGestureRecognizerToFail:myGesture];
}
The only times I've had problems with that are views like UITextView which remove and add gestures as you go in and out of edit mode, so that's a hassle.
Also, while I tried this with standard gestures, I've subsequently shifted to custom gestures that I've programmed to failed as quickly as possible (check the start location and fail immediately if it won't support the direction my gesture requires, rather than waiting for a bunch of touchesMoved to come to the same conclusion). If you don't want to interfere with the child view's gestures, be as aggressive as possible in letting yours fail. Maybe this isn't an issue with a swipe gesture, but it's a possible consideration if you find that your gestures end up changing the behavior of the child view noticeably.
But I suspect you'll probably just have to figure out which views have the gestures that are interfering with yours and make them require yours to fail first.
Any chance you're colliding with one of the scrollview's gestures? It doesn't seem likely if your other gestures are working, but it might be worth at least trying the gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: method in the UIGestureRecognizerDelegate protocol.
I have tracked down the problem to be the locationInView: on the UITapGestureRecognizer always giving me CGPoint of (0,-67) in portrait and (0,268) in landscape. (If i don't cast it as UITapGestureRecognizer, i get (0,180) in landscape occasionally.
This problem does not exist in iOS 5 Simulator. It happens often in iOS 6 Simulator and almost 90% of the time in an iOS 6 Device.
My guess now is that the gesture recognizer is not valid anymore by the time it calls the action method.. but that doesn't make sense as that means we always need to call the locationInView: in the delegate methods..
Details:
What I'm trying to do:
Recognize a Tap gesture on a MKMapView, covert that point to coordinate and display it as an annotation
What I did:
In the action method of the gesture recognizer..
CLLocationCoordinate2D coordinate = [self.mapView convertPoint:[(UITapGestureRecognizer *)sender locationInView:self.mapView] toCoordinateFromView:self.mapView];
I did do introspection to make sure that the sender is indeed an UITapGestureRecognizer..
I also did try return YES for (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer but that does not help.
What should happen:
The coordinate should be corresponding to the point tapped on the map.
What did happen:
The coordinate is always slightly far off to the left.
Update:
So.. i have tested with all the delegate methods in <UIGestureRecognizerDelegate>. Both in the action method as above and – gestureRecognizer:shouldReceiveTouch: the gesture recognizer gives invalid position (0,-64) for locationInView:. (it was 0,-67 as stated above, but become 0,-64 after i updated Xcode to the latest version few minutes ago, lol) However, in – gestureRecognizerShouldBegin: and – gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: it gives the correct CGPoint.
My question is.. is this the intended behavior? Or did i mess up something? Else it means i need to fire my action in one of the delegate methods, as i do need the correct position of the gesture recognizer. This option doesn't sound very right, though..
Sorry for the troubles guys, I have found out the cause.
I simply have left locationInView: in a performBlock: of a NSManagedObjectContext, which is clear, as UIGestureRecognizer is a UI thing, and the Rule #1 in iOS is "UI stuffs only belong to the main thread." This immediately explains the inconsistent behavior of the locationInView: and why it is more likely to succeed in the earlier stage..
Lesson learned, again: Read "gesture recognizer" as UIGestureRecognizer. "UI".
Say I am currently tracking a drag gesture. In my event handler I use a threshold to determine when the drag results in an action. When the threshold is crossed, I want to indicate that the drag gesture has completed.
The only thing I can find in the docs is this line here:
If you change this property to NO while a gesture recognizer is
currently recognizing a gesture, the gesture recognizer transitions to
a cancelled state.
So:
if (translation.y > 100) {
// do action
[self doAction];
//end recognizer
sender.enabled = NO;
sender.enabled = YES;
}
This works but it looks like there might be a neater way.
Does anyone know of another way to indicate that a gesture has ended programmatically? I would expect something like a method -end: that generates a final event with state UIGestureRecognizerStateEnded.
Have you defined a custom UIGestureRecognizer? If the gesture you're recognizing is different from the standard ones defined by Apple because it has a different threshold or is otherwise not the same as a regular UIPanGestureRecognizer, then it might make sense to create your own UIGestureRecognizer. (see subclassing notes)
If you have subclassed UIGestureRecognizer, you can simply set the state like this:
self.state = UIGestureRecognizerStateEnded;
You probably want to do this in the touchesMoved:withEvent: method. Also note:
"Subclasses of UIGestureRecognizer must import UIGestureRecognizerSubclass.h. This header file contains a redeclaration of state that makes it read-write."
On the other hand, if you're only implementing a UIGestureRecognizerDelegate, the state is read-only, and there is no way to directly set it. In that case your method of disabling/enabling might be the best you can do.
With the code you showed you need to have the logic for starting the animation when the gesture recognizer is canceled and I'd say this is not good as there are other ways that this gesture recognizer can be canceled without you wanting to have the animation done.
Considering you have a method for starting the animation you just need to call this method when the threshold is passed and when the gesture ends normally. Two different occasions then.
The code you presented would look like this:
if (translation.y > 100) {
// do action
[self finishFlip];
sender.enabled = NO;
sender.enabled = YES;
}
Canceling the gesture here may be useful also if that prevents any following actions if the user keeps dragging its finger.
If you'll have a team developing over this and you need a specific event to happen you should subclass the gesture recognizer as nont suggested.