Tap on UIBarButtonItem is not ignored by TapGestureRecognizer - ios

I have a view with a UIToolbar with a few UIBarButtonItems and a UITableView containing some UITextFields.
I would like to dismiss the keyboard for a textfield with a tap anywhere. Therefore I added a TapGestureRecognizer to the view. To avoid that the TapgestureRecognizer handles taps on the UIBarButtonItems I added the following method (delegate is set).
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
UIView *view = touch.view;
while (view) {
NSLog(#"Class of view: %#", NSStringFromClass([view class]));
view = view.superview;
}
// Disallow recognition of tap gestures in the toolbar
if ([touch.view isKindOfClass:[UIToolbar class]]) {
return NO;
}
if ([touch.view.superview isMemberOfClass:[UIToolbar class]]) {
return NO;
}
return YES;
}
A UIBarButtonItem is not a view itself, but it has UIToolbar as its superview. When I use the above method, the check for isKindOfClass:[UIToolbar class] does not seem to work for all taps on the toolbar. However the check for the superview with isMemberOfClass:[UIToolbar class] works.
I don't understand this. Maybe someone can explain this behavior?

You shouldn't rely on the view hierarchy around private view classes. It could change at any time.
A better approach is to add the gesture to the table view (or other appropriate view which represents the area you're interested in). Just be sure to enable and disable the gesture at appropriate times so as not to block the usual table operation.

Related

Adding a UIGestureRecognizer taking priority over all other interactions

When I tap on a UIButton, a UIView MyView appear from the bottom a cover a third of the screen. I would like that when I tap somewhere outside this view, it disappears.
I thought about adding another transparent UIView right under MyView and add a tab gesture on it with the dismiss function but I'm sure there is something cleaner than this.
So I thought about adding the tap gesture MyTapGesture to dismiss MyView on self.view of the UIViewController. The problem is that outside this view, I have other UIControls and gestures that capture also any touch at the same time than MyTapGesture.
How can I make MyTapGesture the priority gesture outside MyView and ignore all other gesture, taps, etc...?
You may have to use the gesture delegate methods to handle two tapGestureRecognizer activate the one you need depending on scenario
#pragma mark - UIGestureRecognizerDelegate methods
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
if ([tapGestureRecognizer1 isEqual:gestureRecognizer]) {
return [tapGestureRecognizer2 isEqual:otherGestureRecognizer];
}
if ([tapGestureRecognizer2 isEqual:gestureRecognizer]) {
return [tapGestureRecognizer1 isEqual:otherGestureRecognizer];
}
return NO;
}

How do I ignore the tap if the user tapped on a UIBarButtonItem?

For some reason my UITapGestureRecognizer is blocking my toolbar buttons from being pressed when the recognizer is added to self.view and I don't want it to. In shouldReceiveTouch I want to return NO if the item is the toolbar button.
How do I do this, however? The items aren't UIBarButtonItems apparently, because when I put an if statement to check if touch.view is of that class, it ignores it. If I put a breakpoint there and inspect touch.view its class is UIToolbarTextButton. But [UIToolbarTextButton class] I get a "use of undeclared identifier UIToolbarTextButton" error.
Can I say if it's a subview of UIToolBar? What should I do?
Without any code, this is a hard question to answer... However it sounds like you are adding the Tapgesture recognizer to the same view as your toolbar... You could check the coordinates to see if they are within the CGrect of the uitoolbar... but really what I would recommend is creating a view which contains 2 subviews: one is your toolbar, the other is the main part of your view... then add the tapGesture only to the mainView. Good luck
The better solution is not to add the UITapGestureRecognizer to the self.view.
Add a new view with Interface Builder that covers all the area but the toolbar area.
Put all your controls inside it and add the tap gesture to it.
Alternative: Add this to your ViewDidLoad function or somewhere you find more appropriate.
for (UIView *v in [toolbar items])
{
v.tag = 5; // tag tool bar items
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if (touch.view.tag == 5)
return NO;
return YES;
}

Detect tap on UIView's "empty space"

I've got a view hierarchy that looks like that
UIScrollView
|
+- UIView
|
+- UITextField
+- UITextField
+- UIButton
What I want is for user which tapped one of the text fields and sees the keyboard on the screen to be able to tap on an "empty space" of UIView to hide keyboard. So, I don't want, for instance, an event from UIButton to bubble up to UIView (that's exactly what happens if I add UITapGestureRecognizer to UIView).
How can I achieve the desired functionality?
In your viewDidLoad method add this gesture recognizer:
UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(dismissKeyboard)];
gestureRecognizer.cancelsTouchesInView = NO;
[self.view addGestureRecognizer:gestureRecognizer];
Then add the dismissKeyboard method:
- (void) dismissKeyboard{
[YOURFIELDHERE resignFirstResponder];
}
You also need to add this to make it so the buttons are still clickable and not overridden by the gesture recognizer:
gestureRecognizer.delegate = self; // in viewDidLoad
<UIGestureRecognizerDelegate> //in your header file
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if ([touch.view isKindOfClass:[UIButton class]]){
return NO;
}
return YES; // handle the touch
}
I encounter this same problem and solve it with a naive solution.
Change the View from an instance of UIView to an instance of UIControl so that it can handle touch events.
Create an IBAction method in the View Controller that handles the touch event. In this case, we will resign any first responder from the view's subviews.
- (IBAction)backgroundTapped:(id)sender
{
[contentView endEditing:YES];
}
contentView is just the instance variable pointing to the View. You can name it anything you want. When you passed the message endEditing to the View, it essentially tells its subviews to resign first responder, thus dismissing the keyboard.
Connect the target (View) and action (IBAction method you just created) via Interface Builder, by opening the connection inspector of the View, select Touch Up Inside and drag it to the File's Owner object, then select the name of the method.
Hopefully it helps.
I know it's a little late, but a quick, simple solution is the following:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[self.view endEditing:YES];
}
It gets called if you tap on any empty space.
Mike Z's answer is good. But I think the "if condition" below would be easier and simple in UIGestureRecognizerDelegate when you use Mike Z's answer.
Especially when the subviews are not only a button type, they may also be UITableViewCell, Custom View, etc.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
return touch.view == yourEmptySpaceView;
}

UITextfield's clear button hides keyboard when its inside UIScrollView

I have a textfield inside a UIScrollView and i want to show a clear button when user starts editing. Also i need to hide keyboard when user taps the background of UIScrollview (but not the textfield). Displaying that clear button isn't a problem, the problem is that when clear button is tapped keyboard gets hidden and the text field doesn't get cleared. Obviously the problem is with the gesture recognizer, because method dealing with this gets fired when the clear button is clicked (but it's not fired when the text field is tapped). Here's my code :
//adding gesture recognizer so i can hide keyboard when user taps scrollview
- (void) textFieldDidBeginEditing:(UITextField *)textField
{
if (self.tapOutside == nil) self.tapOutside = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(textFieldTouchOutSide:)];
[self.scrollView addGestureRecognizer:self.tapOutside];
}
//This hides keyboard BUT IS ALSO CALLED WHEN CLEAR BUTTON IS TAPPED
- (void)textFieldTouchOutSide:(id)sender
{
[self.textfield resignFirstResponder];
}
//NEVER GETS CALLED
- (BOOL) textFieldShouldClear:(UITextField *)textField {
return YES;
}
Any ideas how to solve this? Maybe better way to add gesture recognizer? I can't think of no elegant solution ... Thanks a lot in advance...
I had the same problem and solved it implementing the following method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
// Disallow recognition of gestures in unwanted elements
if ([touch.view isMemberOfClass:[UIButton class]]) { // The "clear text" icon is a UIButton
return NO;
}
return YES;
}
Don't forget to conform to the "UIGestureRecognizerDelegate" protocol and set the delegate (using your vars):
self.tapOutside.delegate = self;
Cheers
I was just having this issue and this solution worked, however if you do have other buttons on the view that you allow the user to tap while filling out the form you can do the following:
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
// Disallow recognition of gestures in unwanted elements
if ([touch.view isMemberOfClass:[UIButton class]] && [touch.view.superview isMemberOfClass:[UITextField class]]) {
// The "clear text" icon is a UIButton
return NO;
}
return YES;
}
This will narrow down the case to only return No if the button is a subview of a UITextField, as is the case with the clear button, but still hide the keyboard if they touch a normal button that would normally execute your gesture code.

UIScrollView doesn't scroll when touching GADBannerView subview

I have a project that uses the Google AdMob Ads SDK. I'm trying to show a few ads on the homepage along with some other buttons, some of which are below the screen.
I've used a UIScrollView and added a few GADBannerViews from DFP inside as well the buttons. The ads load just fine and I can click on the ads and buttons with no problem.
The problem is when I try to scroll the scroll view. If I start touching on the ad view, the scroll view will not scroll. If I start touching anywhere else, like a button or a blank space, the scroll view scrolls properly. It seems that the ad is somehow taking control of the touch events.
I've tried all sorts of fixes such as creating a transparent UIView above the ads, which didn't work because the taps would not register.
I've tried looping through the subviews of the GADBannerView but all the subviews' classes seem proprietary to AdMob or inaccessible. (GADWebView, _UIWebViewScrollView)
I even tried adding the ad to a UITableView to see if it would scroll there, but it did not work either.
My view controller class is quite large so if you need me to post some code, I can create a sample app to demonstrate the problem. A workaround for now is to create UIWebViews with the HTML ad code inside instead of using the GADBannerView. I've tested this and it works, but I really don't want to lose the functionality of the native method.
Is there any way to scroll a UIScrollView if you start touching on the GADBannerView and allow the ad to remain clickable?
Thanks!
This issue can be resolved by subclassing UIScrollView, conforming to the the UIGestureRecognizerDelegate protocol, and returning YES from
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
You shouldn't need to set the delegate; UIScrollView is already set as the delegate of it's gesture recognizers by default.
The problem is a conflict between the gesture recognizer in the UIWebView used by GADBannerView and a custom recognizer in GADBAnnerView.
Without subclassing UIScrollView and changing the gesture recognizer delegate, you can remove this gesture recognizer and set your object as delegate for custom recognizer with this:
- (void)preventBannerCaptureTouch:(GADBannerView*)bannerView
{
for (UIWebView *webView in bannerView.subviews) {
if ([webView isKindOfClass:[UIWebView class]]) {
for (UIGestureRecognizer *gestureRecognizer in webView.gestureRecognizers) {
if ([gestureRecognizer isKindOfClass:NSClassFromString(#"GADImpressionTicketGestureRecognizer")]) {
gestureRecognizer.delegate = self;
}
}
for (id view in [[[webView subviews] firstObject] subviews]) {
if ([view isKindOfClass:NSClassFromString(#"UIWebBrowserView")]) {
for (UIGestureRecognizer *recognizer in [view gestureRecognizers]) {
if ([recognizer isKindOfClass:NSClassFromString(#"UIWebTouchEventsGestureRecognizer")]) {
[view removeGestureRecognizer:recognizer];
}
}
return;
}
}
}
}
}
Then you should implement the simultaneous gesture recognizer delegate to allow simultaneously recognise:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
For DFP you can use a DFPSwipeableBannerView instead of a DFPBannerView. Not sure how the orignal GADBanner works tho, but this should be the same. Works in UITableView.
I had to combine two of the answers above:
for (UIWebView *webView in bannerView_.subviews) {
if ([webView isKindOfClass:[UIWebView class]]) {
adView = webView;
}
for (id view in [[[webView subviews] firstObject] subviews]) {
if ([view isKindOfClass:NSClassFromString(#"UIWebBrowserView")]) {
for (UIGestureRecognizer *recognizer in [view gestureRecognizers]) {
if ([recognizer isKindOfClass:NSClassFromString(#"UIWebTouchEventsGestureRecognizer")]) {
[view removeGestureRecognizer:recognizer];
}
}
}
}
webView.scrollView.scrollEnabled = NO;
webView.scrollView.bounces = NO;
}
Where bannerView_ is a GADBannerView
I ran into this issue when trying to add a DFPBannerView as a subview of the contentView of a custom cell in a table view.
For some reason, connecting an IBOutlet defined in my custom cell class to a view in the cell in my storyboard caused the scrolling to start working. The view outlet wasn't even being used, and was completely separate from the banner view - even removing it from its superview allowed the scrolling behaviour to work. Just having an outlet defined and connected to something did the trick.
I wish I could explain why this works, but it remains an iOS mystery.
Unfortunately there is not a not a way to override the scrolling gesture but retain the touch gesture for the ad. The GADBannerView itself needs to control all of the gestures on itself. There is also no way to programmatically send a click to the GADBannerView either, so you can't override the touch behavior either.
I would recommend using ads that are much smaller than your UIScrollView, so you don't have to worry too much about scrolling over an ad.
I solved this by digging down into the GADBannerView and setting the delegate for its web browser view gestures to my own view and then just returning YES for all simultaneous gesture handling:
id webBrowserView = [[[[[[adView subviews] firstObject] subviews] firstObject] subviews] firstObject];
for (UIGestureRecognizer *gestureRecognizer in [webBrowserView gestureRecognizers])
{
[gestureRecognizer setDelegate:self];
}
Then just return yes in the following delegate method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
I ran into the same issue, but decided on a simpler solution. I found that just disabling the ad's web view bounce back allowed the parent scrollview to scroll properly when the ad was touched. Since the ad is the same size as the webview, the only thing the gestureRecognizer was doing was showing the bounce back behavior. Just had to turn that off and left the current gestureRecognizer in place.
- (void)disableBannerBounce:(GADBannerView*)bannerView{
for (UIWebView *webView in bannerView.subviews) {
if ([webView isKindOfClass:[UIWebView class]]) {
webView.scrollView.bounces = NO;
}
}
}

Resources