Why is the UITapGestureRecognizer never getting called with state began? - ios

If you assign a UITapGestureRecognizer to a UIView the UIGestureRecognizerStateBegan doesn't appear when the user has touched the view.
// Tap
_tapGestureRecognizer =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(tap:)];
[_someView addGestureRecognizer:_tapGestureRecognizer];
Instead the recognizer jumps straight to UIGestureRecognizerStateEnded when the user performs the tap.
I have to change that view to a UIButton and listen to the touchDown method.
_someButton = [[UIButton alloc] init];
[_someButton addTarget:self action:#selector(touchDown:) forControlEvents:UIControlEventTouchDown];
[self addSubview: _someButton];
I don't like changing the UIView to a UIButton just for this.
Can I use the UITapGestureRecognizer instead?

Let me start by saying that UITapGestureRecognizer docs clearly tell to expect a callback for all states.
For gesture recognition, the specified number of fingers must tap the view a specified number of times. Although taps are discrete gestures, they are discrete for each state of the gesture recognizer. The system sends the associated action message when the gesture begins and then again for each intermediate state until (and including) the ending state of the gesture. Code that handles tap gestures should test for the state of the gesture, for example:
func handleTap(sender: UITapGestureRecognizer) {
if sender.state == .ended {
// handling code
}
}
Hower it makes little to no sense (specially in case of single tap recognizer). You touched a view (that had the tap gesture added to it), you haven't yet lifted your finger, moved it etc. System can't know at the time of .touchDown event that this interaction is going to turn into a successful recognition of a tap (which requires lifting the finger up).
Essentially UITapGestureRecognizer (for a single touch tap) is a .touchDown + .touchUp combination. If anything else happens after .touchDown like a drag (.touchDragInside OR .touchDragExit), it may lead to successful recognition of a pan gesture (tableView scrolling etc.)
You can think of UITapGestureRecognizer roughly equivalent to .touchUpInside event for a button. A .touchUpInside event for a button doesn't call your function for .touchDown event, It is only possible to receive that event by explicitly asking for the same.
Why do the docs say so?
Maybe system is able to identify the .began state for other scenarios
a multi-tap gesture - double/triple tap (see UITapGestureReconizer.numberOfTapsRequired)
a multi-touch tap - 2/3 finger tap (see UITapGestureReconizer.numberOfTouchesRequired)
You have to test other scenarios for this if you want to know more.

Related

How do I make a function that executes when a tap gesture is lifted in Swift 3.0

Is there any way that i can setup a tapGestureRecognizer in an iOS app, that either sends a signal both when an object is tapped, and when the tap is released, or setup up two tapGestureRecognizer's, one that handles the tap, and one that handles the release?
My tapGestureRecognizer is initialized like this:
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(TapInToSubView))
tapRecognizer.numberOfTapsRequired = 1
sender.addGestureRecognizer(tapRecognizer)
Hope someone out there can help!
You need to set up a UILongPressGestureRecognizer . Set the minimumPressDuration and You can then handle the gesture state methods :
(sender.state == UIGestureRecognizerStateEnded)
(sender.state == UIGestureRecognizerStateBegan
etc. and fire your actions accordingly.
Long-press gestures are continuous. The gesture begins (began) when
the number of allowable fingers (numberOfTouchesRequired) have been
pressed for the specified period (minimumPressDuration) and the
touches do not move beyond the allowable range of movement
(allowableMovement). The gesture recognizer transitions to the Change
state whenever a finger moves, and it ends (ended) when any of the
fingers are lifted.

Give swipe gesture priority over button

I have two UIButtons in a view (one is YES, the other NO). I now want to add a "did not answer", which would be indicated by a downward swipe on the view.
The problem is that the user may swipe down on the view, but in the process hit one of the buttons. When this happens I want to ignore the button press if there was a swipe underway. If it is just a tap on a button, the answer is recorded.
So, if the swipe occurs, I want to call the swipe gesture's action method. If it is determined no swipe occurred but one of the two buttons was touched I want to call their respective action methods. But if a button was touched in the process of a swipe, I want to call only the swipe gesture's action method.
I know there is a way but I'm wondering whether there is an EASY way of doing this. TIA for suggestions.
Since UISwipeGestureRecognizer is a "discrete" gesture, it just triggers a single action when recognized and it won't allow you to detect the end of the gesture.
So to prevent other touches during the gesture, I'd recommend using a UIPanGestureRecognizer instead since it can track your gesture from beginning to end. Then you can try setting your gesture's cancelsTouchesInView property to YES to cancel all other touches in the view that happen while that pan gesture is recognized, ex:
gesture.cancelsTouchesInView = YES;
gesture.delaysTouchesBegan = YES;
Your buttons should be registering touch up in view, so that someone who taps on the button can drag off if they decide not to proceed.
For your other swipe gesture, your buttons will not register a touch up in view during a swipe gesture, even if the swipe passes over the button or ends on it.
Okay, this seems to work. On the gesture recognizer, I used:
UISwipeGestureRecognizer *swipeDown = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(didSwipeDown:)];
swipeDown.direction = UISwipeGestureRecognizerDirectionDown;
swipeDown.delaysTouchesBegan = YES;
swipeDown.delegate = self;
[self addGestureRecognizer:swipeDown];
They delaysTouchesBegan = YES allows it to determine whether the swipe has occurred before passing the touches on to the buttons. So, if you swipe, it calls the swipe GR, and if you touch either button, you get that. Thanks for your answers...

iOS Combining longPress and swipe gesture

I add a swipeUp gesture to the whole view.
I add a longPressGestureRecognizer to the whole view, set its minimunPressDuration equals 0.001f so that it can both detect press down action and touches move action, then call the requireGestureToFail function:
UILongPressGestureRecognizer *longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressed:)];
longPressGestureRecognizer.minimumPressDuration = 0.001f;
[longPressGestureRecognizer requireGestureRecognizerToFail:swipeGestureRecognizer];
The problem is:
When user press and hold (don't move) a button, the longPress gesture's state remains UIGestureStatePossible because the swipeUp gesture doesn't fail, So that it won't react to user touch.
If I don't call requireGestureRecognizerToFail all the gesture including swipeUp gesture will be recognised as longPress gesture.
Implmenting shouldRecognizeSimultaneouslyWithGestureRecognizer: is not what I expect.
What I want is when press and hold(don't move) a button, it triggers longPress, then if user swipe up it triggers swipeUp gesture, if user drags but the touch pattern doesn't fit swipeUp it still triggers longPress.
How can I implement this?
I think it would be easier to implement your own UIGestureRecognizer or UIView subclass with multiple gestures. Check this out:
UITapGestureRecognizer - make it work on touch down, not touch up?
https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/GestureRecognizer_basics/GestureRecognizer_basics.html#//apple_ref/doc/uid/TP40009541-CH2-SW2

Homemade tap gesture recognizer for iOS

I'd like to code my own tap gesture recognizer, to detect the number of taps and number of touches (I don't want to use the iOS tap gesture recognizer because I want to extend it later in various other manners) ;
I tried the following : use the first motionBegin number of touches as the numberOfTouches of the tap, increment the numberOfTaps, and start the tap detection timer to detect the tap gesture if no new taps has been seen in a while
The problem is that one quickly realises that when doing a double-touch tap gesture, iOS either correctly detects one motionBegin with a double touch, or two quick one touch events. I guess a correct implementation should try to detect those quick one touch events that happen closely, but I'm wondering if there is a better way to implement the gesture recognizer.
Someone knows how the iOS tap gesture is implemented?
1. Add UIGestureRecognizerDelegate in your .h file. like
#interface finalScreenViewController : UIViewController <UIGestureRecognizerDelegate>
{
// do your stuff
}
2. Create a view in your viewDidLoad method (or any other method) you wanna to add the gesture in your .m file
ex
UIView * myView=[[UIView alloc]init];
myView.frame=CGRectMake(0,0.self.view.frame.size.width,self.view.frame.size.height);
[self.view addSubView: myView];
UITapGestureRecognizer *letterTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapMethod:)];
letterTapRecognizer.numberOfTapsRequired = 1;
[myView addGestureRecognizer:letterTapRecognizer];
3. you can get view by
- (void) tapMethod:(UITapGestureRecognizer*)sender {
UIView *view = sender.view;
NSLog(#"%d", view.tag);//By tag, you can find out where you had tapped.
}

detecting finger up/down UITapGestureRecognizer

How can I know when the finger is down and when is it up with UITapGestureRecognizer?
The documentation says I should only handle UIGestureRecognizerStateEnded as tap so it means there is UIGestureRecognizerStateBegin when finger is down, but all I get is UIGestureRecognizerStateEnded.
The code I use to register the recognizer is:
[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tap:)]
UITapGestureRecognizer is a discrete gesture recognizer, and therefore never transitions to the began or changed states. From the UIGestureRecognizer Class Reference:
Discrete gestures transition from Possible to either Recognized
(UIGestureRecognizerStateRecognized) or Failed
(UIGestureRecognizerStateFailed), depending on whether they
successfully interpret the gesture or not. If the gesture recognizer
transitions to Recognized, it sends its action message to its target.
(Remembering of course that UIGestureRecognizerStateRecognized == UIGestureRecognizerStateEnded).
The docs are saying that you should check the state of a tap gesture recognizer to see that it is in its ended state, before you fire your code to say that it has been recognized. They are not saying that the tap gesture actually transitions to the began or changed states (although I admit that the docs are a little misleading in the language used!).
If you want to check for the finger down event for a tap gesture recognizer, I would recommend just using touchesBegan:withEvent:, since this is what you are really after anyway.
You could override the delegate method -(BOOL)gestureRecognizer:shouldReceiveTouch:.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
NSLog(#"Hello from press down");
return YES;
}

Resources