UIButton with both Touch Up Inside and Touch Repeat? - ios

What's the best way to get a button to be capable of both a Touch Up Inside action and a Touch Down Repeat action? Kind of like the Home button on an iPhone: one click goes to the home screen, but two rapid clicks opens the multitasking bar. Right now my button has both methods hooked up, but the Touch Up Inside method (unsurprisingly) gets called before the Touch Down Repeat can happen.
I can come up with a few ways I might pull this off (having the Touch Up Inside method wait a second to see if another click comes before executing, having a second button move invisibly into place after the first click, etc) but they all seem kind of hack-y and open to performance losses and bugs. I found the tapCount property, but it's for UITouch instead of UIButton, so if([_buttonAddItems tapCount] > 1) {} else if([_buttonAddItems tapCount] == 1), which seems like it would be the most efficient way of doing it, doesn't work.
Is there a best-practice for this sort of thing? Or if not, does anyone have a preferred way of getting this done?

Try disconnecting both touch events from your button, and hook two tap gesture recognizers to it instead. Here is a link to an answer that explains how to set up two gesture recognizers so that one of them recognizes a single tap, and the other one recognizes a double tap:
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget: self action:#selector(doSingleTap)];
singleTap.numberOfTapsRequired = 1;
[myButton addGestureRecognizer:singleTap];
UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget: self action:#selector(doDoubleTap)];
doubleTap.numberOfTapsRequired = 2;
[myButton addGestureRecognizer:doubleTap];
// This is the "secret sauce":
[singleTap requireGestureRecognizerToFail:doubleTap];

Related

Why is the UITapGestureRecognizer never getting called with state began?

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.

UIGestureRecognizers for single and double tap set in the xib

I have set two UITapGestureRecognizers in my xib on a UIImageView. I have also set their IBAction in the associated header file.
For the single tap gesture recognizer, I set taps and touches to 1, state to Enabled, and delayed touches ended to YES in the Attributes inspector.
For the double tap gesture recognizer, I set taps and touches to 2, state to Enabled, cancel touches in view to YES and delay touches ended to YES.
When I double tap on the UIImageView, it only triggers the IBAction method for the single tap. So, I decided to print the imageview.gestureRecognizer and it shows the UITapGestureRecognizer for single tap's state as Ended and the UITapGestureRecognizer for double tap's state as Possible.
I have been stuck on this for a couple hours. I found ways to do it programatically but I was wondering how I can do it by setting it in the xib itself.
Any help would be great! Thank you in advance for your responses!
It's a very good question. If you add gestures to code like this
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget: self action:#selector(singleTap)];
singleTap.numberOfTapsRequired = 1;
[self.view addGestureRecognizer:singleTap];
UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget: self action:#selector(doubleTap)] ;
doubleTap.numberOfTapsRequired = 2;
[self.view addGestureRecognizer:doubleTap];
[singleTap requireGestureRecognizerToFail:doubleTap];
And all works fine because you canceled first gesture here
[singleTap requireGestureRecognizerToFail:doubleTap];
If you add two gestures in xib you always should cancel single tap if there was a double tap. And you always need to use 2 properties for gestures and use
[self.firstGestureProperty requireGestureRecognizerToFail:self.secondGestureOroperty];
For single tap:
For double tap:
Source code:
And everything works fine.

Detect touch over TableView and still be able to scroll

I need to be able to detect immediate touch and get its position. (so didSelectRowAtIndexPath can't help us since it does not act immediately when scrolling up and down fast, you need to breathe in and select one by one)
Already tried everything I can think of. Touches began in each cell does not work because it suddenly behaves like didSelectRowIndexPath when implemented in custom cell class. Same result with TableViewController, the nature of touches began (you touch it, respond right away) just won't work.
* I'm not trying to TAP. Need to be able to get TOUCH (TapGesture does not respond when swiping very carefully/slowly but touches began always does) *
Not sure it's what you need, but you can create a TapGestureRecognizer.
You will likely run into conflicts with the UITableView's own gesture recognisers, but there are mechanisms to solve these which should hopefully let you achieve your desired behaviour (look up requireGestureRecognizerToFail and UIGestureRecognizerDelegate's gestureRecognizerShouldBegin and shouldRecognizeSimultaneouslyWithGestureRecognizer).
Try this.
In cellForIndexpath method.
UITapGestureRecognizer *singleFingerTap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(handleSingleTap:)];
singleFingerTap.delegate=self;
cell.contentView.tag=indexPath.row;
[cell.contentView addGestureRecognizer:singleFingerTap];
//The event handling method
- (void)handleSingleTap:(UITapGestureRecognizer *)recognizer {
NSLog(#"%ld",(long)recognizer.view.tag);
}
I think what you need to do, add UILongPressGestureRecognizer to tableview, so normal touch will scroll and long press will do whatever you want to. Set its minimumPressDuration like 0.2 so it won't take much time. Add action for UILongPressGestureRecognizer and in that method get location like:
CGPoint touchPointInView = [sender locationInView: self.view]; //location reespective to view
CGPoint touchPointInView1 = [sender locationInView: tableView]; //location respective to tableview

addTarget:action:forControlEvents: ignored when interacting with separate UITapGestureRecognizer

I'm somewhat new to iOS programming
I have some code (abridged) that looks like the following
UIView *someSubView = [[UIView alloc] initWithFrame:...];
[self addSubView:someSubView];
[someSubView addTarget:self action:#selector(_handleTapOnView:) forControlEvents:UIControlEventTouchUpInside];
_tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(_handleTap:)];
_tapGestureRecognizer.delegate = self;
[self.view addGestureRecognizer:_tapGestureRecognizer];
Unfortunately the gesture recognizer triggers and my views addTarget call does not. I've tried commenting out the gesture recognizer code and it works, so I know its not the call to addTarget on the subview.
I solved this initially by using the gestureRecognizer:shouldReceiveTouch: and doing a hit test for the sub view, but I feel like I'm missing some fundamental understanding here that wouldn't require me adding a manual hit test.
Its important to note that I don't want the code in the _handleTap in the _tapGestureRecognizer to execute when I have tapped on my subview.
Any guidance here? Thanks!
Try using:
_tapGestureRecognizer.cancelsTouchesInView = NO;
otherwise the gesture recogniser will intercept the touches and will not forward them further (in other words, the gesture recogniser gets the touch, handles it, and since it cancel it, no other object gets the touch). By not cancelling, the touch is forwarded for any other object (recognisers or views) to handle it.

UITapGestureRecognizer double click slows down release

I 've an annoying problem.
I' m adding a gesture recognizer:
UITapGestureRecognizer* tapGesture =[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(DoubleClick:)];
tapGesture.numberOfTapsRequired = 2;
[self.view addGestureRecognizer:tapGesture];
This works, but when I single click any control that is on my view, the "releasing" is slow. I.e. a UIButton is released more slowly than normally. The same happens for all my controls inside the UIView. The touchesEnded: function is called with a delay.
When I use tapGesture.numberOfTapsRequired = 1, it works fine. However I want double click, not single click.
Please advise.
Thx.
When tap and let go once, how do you know if it's a single tap or just the first half of a double tap?
Answer: You wait. If the second tap comes, it was a double tap. If a certain amount of time passes and no second tap happened, then it was a single tap. Check out delaysTouchesEnded on UIGestureRecognizer for more information on it.
I get around this issue by creating gesture which won't conflict with each other. A "two finger tap" and a "one finger tap" won't cause a delay, because you'll know how many finger were used before the gesture ends.

Resources