UITableview didSelectRowAtIndexPath supercedes double tap gesture by UITapGestureRecognizer - iphone

I've set up a UITableView with a double-tap UITapGestureRecognizer. But attempts to double-tap a cell by the user just launches didSelectRowAtIndexPath twice. Are these two supposed to work together?
(i'm aware i could use a single tap gesture recognizer in place of the built-in behavior of didSelectRowAtIndexPath, but here's the problem with that: the cell also has a button that I can't press anymore when i add the single tap gesture recognizer. Also, I've seen examples on SO of users building double tap functionality into didSelectRowAtIndexPath, but isn't that a bit too much of a hack?)

More info about Kris' answer:
cancelsTouchesInView
delaysTouchesBegan
delaysTouchesEnded
http://developer.apple.com/library/ios/#documentation/uikit/reference/UIGestureRecognizer_Class/Reference/Reference.html
In my case I had a problem when adding 2 tap gestures to a UIImageView that was on a custom UITableViewCell. What happened was the didSelectRowAtIndexPath: was called when you tap/double tap on the UIImageView. When I had only one tap gesture, the didSelectRowAtIndexPath: was not called (and for me, that was the right behavior.
To prevent the didSelectRowAtIndexPath: from being called when using two tap gestures (single/double tap), I added this code to the first tap gesture (the single one):
tapGesture.cancelsTouchesInView = YES;
tapGesture.delaysTouchesBegan = YES;
After this change, a tap/double on the UIImageView (on top of the custom cell) did not trigger the didSelectRowAtIndexPath:

Looks like I can get didSelectRowAtIndexPath and the double tap gesture recognizer to play together nicely using the delaysTouchesBegan and cancelsTouchesInView properties of the gesture recognizer.
The other option described by #MSgambel seems to work equally well.

You can use a single-tap gesture recognizer in place of didSelectRowAtIndexPath, even if there is a button in the cell. You just need to check if the touch location is inside the UIButton's view or not in order to handle both cases. Hope that Helps!

I tested the "delayTouchesBegan" method with double taps but I found that the single tap received by the table is then delayed, making the table interaction less responsive to the user, and perhaps annoying.
My solution is a bit pedestrian but I use a timer to detect taps in method didSelectRowAtIndexPath. I record the tap count of "1" for the first tap, and if the user does not tap again with 0.2 seconds it displays the item selected. If the user tapped within 0.2 seconds for tap count "2" then I display another item (an action sheet). I reset the tap count each time.
This method uses more code, but provides quick response from the interface and the user does not need to know what is happening behind the scenes - just that the UI is responsive.

Related

When to use IBAction vs. UITapGestureRecognizer?

I have seen storyboard buttons combined with IBAction code as well as UITapGestureRecognizers (not speaking here of programtically defined buttons).
I am curious if there is any (not strongly opinionated) reason to prefer one over the other in specific situations.
Buttons have their own tap action events.
All other UI elements have no.
E.g., if you want to handle label tap event you have to use gesture recognizers.
UIButtons have actions. you don't need tap gesture for it. You may need tap gesture for UILabel or UIView.

Is it possible to add a custom gesture recognizer to a view in TVOS app?

In my TVOS app I created a custom gesture recognizer, which is a subclass of a UIGestureRecognizer.
Later in the code I'm trying to add it to a collection view cell.
let customGest:CustomGestureRecognizer = CustomGestureRecognizer(target: self, action: Selector("myMethod:"))
cell.addGestureRecognizer(customGest)
in the debugger I see that my gesture recognizer is getting initialized properly. However, none of it's touches methods are getting called (touchesBegan, touchesMoved..).
I've done this in iOS just fine, so I'm curious if it is possible to do so in TVOS?
Any kind of help is highly appreciated.
Touch events (UITouch) and button press events (UIPress) are first delivered to the focused view, and then they go up the responder chain from there. So your custom gesture recognizer will only fire if the cell you added it to is focused, or if the cell contains the focused view as a descendant.
Is the cell you're adding this gesture to focused (or contain the focused view)?

Swipe gesture recognizer not working in vertical directions

guys, I'm totally new to Objective C and XCode, so don't be too harsh on me for such a question. In fact, this is my first question on StackOverflow ever. The problem is the following:
I need functionality for closing the keyboard when tapping or scrolling anywhere outside the textField that is currently first responder.
I managed to implement it for tap and horizontal swipes with the following code (right swipe example):
UISwipeGestureRecognizer *swipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(closeKeyboard:)];
swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionRight;
[self.tableView addGestureRecognizer:swipeGestureRecognizer];
swipeGestureRecognizer.cancelsTouchesInView = NO;
And in fact, later I figured out how to do it using solely interface builder. But vertical swipes just would not work, neither when I do them programmatically, nor through the IB. I suspect that the problem is that my tableView scrolls vertically on the screen, thereby preventing the call of the swipe, but I still have no idea on how to overcome this issue.
I would greatly appreciate your help! Thanks.
I saw, that you are trying to add this vertical gesture to a tableView. So you don't need to add a vertical swipe gesture . You have to implement UIScrollViewDelegate protocol, and implement the -scrollViewDidScroll: or -scrollViewWillBeginDragging: methods. In these methods you can catch if your tableview scrolling, and if your tableView is scrolling, call your closeKeyboard method
the UITableView basically uses UIScrollView & when you implement the UITableView delegate, you also implement the UIScrollView delegate (because behind the scenes, the UITableViewDelegate has implemented the UIScrollViewDelegate)
You do not have to manually implement the UIScrollViewDelegate & you can simply start using any UIScrollView delegates you want such as the -(void)scrollViewDidScroll:
But if you want vertical swipes to work on the tableview then it can be done in the following dirty & totally buggy technique. (Basically this is how NOT to do it)
MAIN property is setDelaysContentTouches on your tableView object to NO.
setDirection on your UISwipeGestureRecognizer object to detect vertical up & vertical down swipes.
Set the swipe gesture onto the tableView object.
example:
[self.tableView setDelaysContentTouches:NO];
UISwipeGestureRecognizer *mySwipe = [[UISwipeGestureRecognizer alloc]
initWithTarget:self
action:#selector(closeKeyboard:)];
[mySwipe setDirection: UISwipeGestureRecognizerDirectionUp | UISwipeGestureRecognizerDirectionDown];
[self.tableView addGestureRecognizer:mySwipe];
Finally, you'll get it to work but it works real bad.
Most of the times, the swipe is ignored since you need to swipe
really really fast and cover a decent distance within the swipe action.
When the swipe works, the table does not scroll.
So, as you see, it's really sad and dirty thing to do.
The reason is that the scrollView handles the content touches in a very specific way.
To decide if the touch is to be handled or to be forwarded, the
UIScrollView starts a timer when you first touch it:
If you haven't moved your finger significantly within 150ms, it passes the event on to the inner view.
If you have moved your finger significantly within 150ms, it starts scrolling (and never passes the event to the inner view). Note:
how when you touch a table (which is a subclass of scroll view) and
start scrolling immediately, the row that you touched is never
highlighted.
If you have not moved your finger significantly within 150ms and UIScrollView started passing the events to the inner view, but then
you have moved the finger far enough for the scrolling to begin,
UIScrollView calls touchesCancelled on the inner view and starts
scrolling. Note: how when you touch a table, hold your finger a
bit and then start scrolling, the row that you touched is highlighted
first, but de-highlighted afterwards.
These sequence of events can be altered by configuration of
UIScrollView:
If delaysContentTouches is NO, then no timer is used — the events immediately go to the inner control (but then are canceled if you move
your finger far enough)
If cancelsTouches is NO, then once the events are sent to a control, scrolling will never happen.
Note that it is UIScrollView that receives all touchesBegin,
touchesMoved, touchesEnded and touchesCanceled events from CocoaTouch
(because its hitTest tells it to do so). It then forwards them to the
inner view if it wants to, as long as it wants to.
source: https://stackoverflow.com/a/844627/2857130

delaying UITapGestureRecognizer

I have a UITapGestureRecognizer which basically performs an action to add a subview. When I tap I only want the subview to load once, however when I tap twice really fast, it performs the action twice. How can I prevent this? Basically after it's tapped once I want the tap gesture recognizer to be disabled temporarily for some seconds. Is there a way to do this?
Disable your tap recognizer in the selector that the recognizer calls. Override didAddSubview in the view to which you add subviews on tapping the recognizer, and re-enable it from there. The recognizer will remain inactive through the time while the new subview is being added. If you animate the addition, you should get a sufficient delay to avoid reacting to double-taps.
You could always use a selector with a delay.
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay
I would also look into using an NSTimer object.

UIButton touch event falls through to underlying view

I have created a small UIView which contains two UIButtons. The view responds to UITapGesture events. The Buttons are supposed to respond to TouchUpInside, however when I tap the buttons the responder is the underlying view and the tap gesture selector is triggered. Looking for advice or suggestions.
You can modify the method that responds to the tap gesture in the orange view:
-(void) handleTapFrom:(UITapGestureRecognizer*) recognizer {
CGPoint location = [recognizer locationInView:orangeView];
UIView *hitView = [orangeView hitTest:location withEvent:nil];
if ([hitView isKindOfClass:[UIButton class]]) {
return;
}
//code that handle orange view tap
...
}
This way if you touch a UIButton, the tap will be ignored by the underlying view.
The right answer (which prevents the tabrecognizer from highjacking any taps and doesn't need you to implement a delegate etc) is found here. I got a lead to this answer via this post.
In short use:
tapRecognizer.cancelsTouchesInView = NO;
"Which prevents the tap recognizer to be the only one to catch all the
taps"
Each UIView has an 'exclusiveTouch' property. If it's set to YES the view won't pass the touch down the responder chain. Try setting this property on your UIButtons and see if that makes a difference.
How are the views ordered in the view hierarchy? Also, are you creating the interface in IB? If you hook up the connections properly, this shouldn't be an issue at all...
The problem with this design is that it's similar to embedding one button into another. While you may have a valid reason to do it, you should be more careful with the event flow.
What happens is the gesture recognizer intercepting touches before they reach subviews (the two buttons in your case). You should implement UIGestureRecognizerDelegate protocol, namely, gestureRecognizer:shouldReceiveTouch: method, and return NO if the touch is inside any of the two buttons. That will prevent the orange view from usurping touches intended for the buttons and the buttons will start to work as expected.
Check out this question.
uibutton-inside-a-view-that-has-a-uitapgesturerecognizer