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.
}
}
Related
Now I have implemented the fling feature in my ios app, but I want to stop the fling when a touch on screen happens, this does not only contain tap gesture, maybe other like long press or pinch, I have a BOOL value to indicate whether a touch happens, so should I add all kinds of gesture recognizers and set to BOOL value as true? Are there any simple solution? Thank you!
There are several methods in UIGestureRecognizerDelegate that will achieve what you want. gestureRecognizerShouldRegognizeSimultaneouslyWithGesture sounds like the best candidate. You will get passed two gesture recognizers. One will be your swipe gesture (the one you want to keep), the other will be the one you want to cancel (tap, long press, pinch) return NO in the correct scenario.
See docs: https://developer.apple.com/documentation/uikit/uigesturerecognizerdelegate
I'm using a UIGestureRecognizer to recognize a single tap, a double tap and a longpress.
What I would like to do is also recognize a longpress, then swipe to either left or right.
Would this be possible given that I'm already consuming a longpress? I'm confused on this one and would appreciate pointers on how to do.
Thanks
Just tried this out myself and it seems as if the UILongPressGestureRecognizer will transition to its end state as soon as the UISwipeGestureRecognizer begins. Just make sure shouldRecognizeSimultaneouslyWithGestureRecognizer: returns YES for this gesture combination.
You'd need to use two gesture recognisers and make sure you track the state of the long press one when you receive a callback to say it's ended, and then do something based on the swipe/pan gesture following it.
I am developing a app in which I have a view which contains subView in it.
I want to track both swipe and tap actions such as a single click.
Actions should be tracked only when the user touches within my subview. When the user taps I want to perform one action, when the user swipes I want perform another.
For tracking the swipe, I implemented UIGestureRecognizer and it is working fine. But I don't know how to track the tap option. Please guide me how to achieve this.
The main thing is, when I tap it should call tap action only and vice versa.
You can use UITapGestureRecognizer for tap gestures.
"UITapGestureRecognizer is a concrete subclass of UIGestureRecognizer
that looks for single or multiple taps. For the gesture to be
recognized, the specified number of fingers must tap the view a
specified number of times."
This method includes the numberOfTapsRequired ("The number of taps for the gesture to be recognized.") and numberOfTouchesRequired ("The number of fingers required to tap for the gesture to be recognized") properties where you can set exactly how you want it to react to user action.
In this case, as you only want it to be activated when tapped once, the default settings for both these properties (both have default values of 1) should be fine.
The best place to get the information is Defining How Gesture Recognizers Interact of Event Handling Guide for iOS
When a view has multiple gesture recognizers attached to it, you may
want to alter how the competing gesture recognizers receive and
analyze touch events. By default, there is no set order for which
gesture recognizers receive a touch first, and for this reason touches
can be passed to gesture recognizers in a different order each time.
You can override this default behavior to:
Specify that one gesture recognizer should analyze a touch before another gesture recognizer.
Allow two gesture recognizers to operate simultaneously.
Prevent a gesture recognizer from analyzing a touch.
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.
I have two UIGestureRecognizers for a view that recognizes both simultaneously. I'd like for the finish or cancellation of the primary gesture to kill the other gesture as well. So, is there a way to kill an active gesture, i.e. force the cancellation of an active gesture recognizer?
Since you want to kill the secondary gesture only when the primary gesture has ended or cancelled, do this in the gesture handler of the primary gesture.
- (void)handleGesture:(UIGestureRecognizer*)gesture {
...
if ( gesture.state == UIGestureRecognizerStateEnded || gesture.state == UIGestureRecognizerCancelled ) {
secondaryGesture.enabled = NO;
secondaryGesture.enabled = YES;
}
}
This seems to be the only way you can cancel a gesture.
You can use requireGestureRecognizerToFail: to declare a dependency.
[secondaryGesture requireGestureRecognizerToFail:primaryGesture];
This will kill the secondary gesture on successful identification of the primary gesture. There is no such tool provided if the primary gesture is cancelled. You can probably flip the enabled flag of the secondary gesture to NO and YES in the gesture handler of the primary gesture on UIGestureRecognizerStateCancelled but that doesn't seem elegant.
Depending on the gestures you may want to have both. I ran up against a problem where gestures were interfering with one another and for a while had something similar to this solution. The two gestures I was trying to use was the pinch and rotate gesture. Ideally I wanted the user to be able to smoothly transition between the two without having to cancel one which I managed to achieve. I have written up how I did it here and provided a how to video I hope this might help someone.