8

I've currently have an iPad app with a UIToolbar containing two UIBarButtonItems, each of which is connected to a popover segue.

When the user touches either of the UIBarButtonItems, the popover is created rather than toggled. This creates multiple, overlapping popovers. I've been able to close the previously created popover using the following code

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // First close the preferences popover if it's open per Apple guidelines
    if ([self.popoverA.popoverController isPopoverVisible]) {
        [self.popoverA.popoverController dismissPopoverAnimated:YES];
    }

    if ([self.popoverB.popoverController isPopoverVisible]) {
        [self.popoverB.popoverController dismissPopoverAnimated:YES];
    }
    ... code to manage segues ...
}

I also have UIButtons which create popover segues which behave normally. Due to this behavior of the popovers associated with UIBarButtonItems, my app is being rejected. Does someone have any suggestions or any code samples of a UIToolbar with multiple UIBarButtonItems that work correctly? The popovers do dismiss when the user touches outside the window,

Timothy Newton
  • 135
  • 1
  • 7
  • Just to clarify my problem. The expected behavior is that each time the button (UIBarButtonItem) is touched, the popover should be toggled open or closed rather than always opened. The segue, however, appears to always create another popover, rather than dismissing an existing visible one. When linked to a UIButton, the popovers are toggled appropriately. – Timothy Newton Dec 22 '11 at 01:47

3 Answers3

16

This is the proper way to do what you need to do:

- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
    if ([identifier isEqualToString:@"SurveyListPopover"]) {
        if (self.surveyListPopover == nil) {
            return YES;
        }
        return NO;
    }
    return YES;
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"SurveyListPopover"]) {
        // Assign popover instance so we can dismiss it later
        self.surveyListPopover = [(UIStoryboardPopoverSegue *)segue popoverController];
    }
}

This ensures that the segue will be cancelled if an instance of the popover has already been displayed. You just need to make sure your popover object has an identifier in the storyboard.

Brenden
  • 7,708
  • 11
  • 61
  • 75
  • If you're targeting 6.0+, this is the best approach yet. – Mark Adams Feb 20 '13 at 05:57
  • is it just me or in ios 5 shouldPerformSegueWithIdentifier is not getting called at all? – Radu Simionescu Mar 10 '14 at 07:36
  • https://developer.apple.com/library/ios/documentation/uikit/reference/UIViewController_Class/Reference/Reference.html#//apple_ref/occ/instm/UIViewController/shouldPerformSegueWithIdentifier:sender: Seems it was introduced in iOS 6.0 and up – Brenden Mar 12 '14 at 16:09
8

By the time you get messaged in -prepareForSegue:sender:, it's too late to cancel a segue.

In order to do this efficiently, you should create segues to your popovers from the view controller itself instead of the bar buttons so that they can still be programmatically executed. Now wire the UIBarButtonItems up to some methods that will conditionally present or dismiss the popover.

- (IBAction)showPopoverA
{
    if (self.popoverA.popoverController.popoverVisible)
        [self.popoverA.popoverController dismissPopoverAnimated:YES];

    [self performSegueWithIdentifier:@"ShowPopoverA"];
}
Mark Adams
  • 30,776
  • 11
  • 77
  • 77
  • 1
    After trying that suggestion, I'm getting the following error: **Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UIStoryboardPopoverSegue must be presented from a bar button item or a view.** – Timothy Newton Dec 22 '11 at 02:55
  • `- (IBAction)showPopoverA:(id)sender { if ([self.popoverA.popoverController isPopoverVisible]) { [self.popoverA.popoverController dismissPopoverAnimated:YES]; } [self performSegueWithIdentifier:@"showPopoverASegue" sender:sender]; }` – Timothy Newton Dec 22 '11 at 02:55
  • `- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([[segue identifier] isEqualToString:@"showPopoverASegue"]) { UINavigationController *navController = [(UIStoryboardPopoverSegue *)segue destinationViewController]; PreferencesVC *dvc = [[navController viewControllers] objectAtIndex:0]; [dvc setUserPrefs:self.userPrefs]; [dvc setDelegate:self]; // used to close popover [self setPrefsPopover:(UIStoryboardPopoverSegue *)segue]; [self.prefsPopover.popoverController setDelegate:self]; }` – Timothy Newton Dec 22 '11 at 02:56
  • Sorry for the lack of carriage returns. Can't seem to be able to format my comments to be more readable. – Timothy Newton Dec 22 '11 at 02:59
  • Figured it out. You have to set the anchor of the segue back to the appropriate UIBarButtonItem. Also, found that to toggle, the [self performSegueWithIdentifier:sender] should be in an else clause (otherwise the problem comes back). Thanks again for the tip, it set me on the right track. – Timothy Newton Dec 22 '11 at 03:12
1

Combination of both made it for me

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"showPopover"]) {
        self.tableOfContentsPopoverController = [(UIStoryboardPopoverSegue*)segue popoverController];
    }
}

- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
    if ([identifier isEqualToString:@"showPopover"]) {
        if (!self.tableOfContentsPopoverController.popoverVisible) {
            return YES;
        }
        return NO;
    }
    return YES;
}
Sebastian Boldt
  • 5,283
  • 9
  • 52
  • 64