Question

I've got a UIToolbar at the top of my iPad app's main screen. It has 6 UIBarButtonItems on it. 4 of these items trigger UIPopOvers to appear. The other 2 items either switch to a different view or change something about the current view.

3 of the 4 UIPopOvers appear from the tapped UIBarButtonItem, the 4th appears without an arrow in the middle of the screen.

I would like the following functionality, but am having difficulty getting to it:

  1. When no UIPopOvers are displayed and a user taps one of the UIBarButtonItems that generates a UIPopOver, show that UIPopOver (pretty simple; got this working.)
  2. When a UIPopOver is displayed and a user taps the UIToolbar, dismiss that UIPopOver. (Got this working using a UIGestureRecognizer that takes 1 tap.)
  3. When a UIPopOver is displayed and a user taps the UIBarButtonItem TO WHICH THE UIPopOver IS "TIED", dismiss the UIPopOver. (Here's the problem for me.)
  4. When a UIPopOver is displayed and a user taps another UIBarButtonItem, dismiss the current UIPopOver and show the appropriate new one. (This is working OK.)

So, the problem that I'm facing is that the UIGestureRecognizer fires before the button tap. I also can't find a good way to "opt out" of the UIGestureRecognizer when the user is pressing a UIBarButtonItem (thus, only firing the UIGestureRecognizer's action when the UIToolbar itself is tapped, not a UIBarButtonItem). The end result of this is that when a UIPopOver is displayed (from a UIBarButtonItem), and the user taps the same UIBarButtonItem, the UIPopOver is dismissed and then it shows up again.

I'm trying to avoid some sort of timing issue where I set a "toolbarTapped" flag to YES for 0.10 seconds and then set it back to NO (or something like that).

I'd like to find a way to truly do this elegantly (and not hack-y).

I can't seem to find a way to determine when the UIGestureRecognizer was triggered based on a UIBarButtonItem touch since the UIGestureRecognizer fires first and there doesn't appear to be a good (non-private) way to get the frame of a UIBarButtonItem.

Basically, I'm trying to make the UIToolbar and its UIBarButtonItems behave the way any reasonable person would expect, but I'm beating my head against the wall.

Here's the code for the UIGestureRecognizer:

// Initialization
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(removeAllPopOvers)];
[tapRecognizer setCancelsTouchesInView:NO];
[tapRecognizer setNumberOfTapsRequired:1];
[tapRecognizer setNumberOfTouchesRequired:1];
[[self Toolbar] addGestureRecognizer:tapRecognizer];

// Tap handler
- (void)removeAllPopOvers {
    NSLog(@"removing all popovers");
    if ([self firstPopOver]) {
        [[self firstPopOver] dismissPopoverAnimated:YES];
        [self setFirstPopOver:nil];
    }
    // and so on with the rest...
}

And here's how one of my UIPopOvers is shown:

- (IBAction)showSettings:(id)sender {
    NSLog(@"settings button tapped");
    if (![self SettingsPopOver]) {
        SettingsViewController *settingsVC = [[SettingsViewController alloc] initWithNibName:@"SettingsView-iPad" bundle:nil];
        UIPopoverController *popOver = [[UIPopoverController alloc] initWithContentViewController:settingsVC];
        [popOver setDelegate:self];
        [self setSettingsPopOver:popOver];
        [[self SettingsPopOver] setPopoverContentSize:CGSizeMake(320, 300)];
        [[self SettingsPopOver] presentPopoverFromBarButtonItem:[self Settings] permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
    }
}

The net effect of all of this is that I can choose one of the follow two options (but not both):

  1. Allow the UIBarButtonItem to dismiss the UIPopOver.
  2. Allow tapping the UIToolbar to dismiss the UIPopOver.

Any ideas?

Thanks!

Was it helpful?

Solution

For now, I've implemented something that works, but isn't elegant (at least, I don't think it is).

Basically, what I do is delay the action from the UIGestureRecognizer by sending it first to a "buffer" method:

#pragma mark - PopOver Dismissal
// dismissAllPopOversBuffer is called as the action of my UIGestureRecognizer
- (void)dismissAllPopOversBuffer {
    NSLog(@"dismiss all popovers buffer...");
    [self performSelector:@selector(dismissAllPopOvers) withObject:nil afterDelay:0.1];
}
- (void)dismissAllPopOvers {
    NSLog(@"dismissing all popovers");
    // actual dismissal logic
}

As you can see, this pushes the actual dismissal logic out 0.1 seconds. Then, in each of the button press methods, I do this:

#pragma mark - UIBarButtonItem Press Event Handlers
- (IBAction)buttonPressed:(id)sender {
    [NSObject cancelPreviousPerformRequestsWithTarget:self
                                             selector:@selector(dismissAllPopOvers)
                                               object:nil];
    // The rest of the logic to dismiss/show UIPopOver
}

So far, my testing shows it to work. Based on NSLog timestamps, the actual elapsed time between the UIGestureRecognizer action and the UIBarButtonItem event was generally 1 millisecond (0.001 seconds), so pushing out 0.1 seconds (or 100x the normal time lapse) SHOULD be safe most of the time, but I still don't like it.

I'd love to find a way to determine when the user has tapped on the UIToolbar but not any of the UIBarButtonItems. That seems fairly straightforward (logically) but fairly difficult (practically).

OTHER TIPS

for issue 3:

if ([self.popoverController isPopoverVisible])
    [self.popoverController dismissPopoverAnimated:YES];
else

if the popover is visible, it will dismiss it; else you create the popover.

for the rest, do you only want the popover to dismiss if the tool bar is touched? if the user touches anywhere else on the screen, you want the popover to stay?

popover controller has a passthroughViews proper that i believe allows you to add to it a uiview, so in effect if you add in a main view and its touched, it won't trigger the popover dismiss. i'm not completely sure how this works, you'll need to do some digging.

https://developer.apple.com/library/ios/#documentation/uikit/reference/UIPopoverController_class/Reference/Reference.html

So I solved having 2 gestures recognized at the same time by implementing the Gesture Recognizer delegate and then calling the method below (For me this allowed my pan gesture for scrolling and a swipe gesture to call a new view to pop up to register at the same time):

(BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer      
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer
{
  return YES;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top