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:
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):
Any ideas?
Thanks!
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).