Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Dismiss a Storyboard Popover

I've created a popover from a UIBarButtonItem using Xcode Storyboards (so there's no code) like this:

Xcode 5.0 Connections Inspector with Popover

Presenting the popover works just fine. However, I can't get the popover to disappear when I tap the UIBarButtonItem that made it appear.

When the button is pressed (first time) the popover appears. When the button is pressed again (second time) the same popover appears on top of it, so now I have two popovers (or more if I continuer pressing the button). According to the iOS Human Interface Guidelines I need to make the popover appear on the first tap and disappear on the second:

Ensure that only one popover is visible onscreen at a time. You should not display more than one popover (or custom view designed to look and behave like a popover) at the same time. In particular, you should avoid displaying a cascade or hierarchy of popovers simultaneously, in which one popover emerges from another.

How can I dismiss the popover when the user taps the UIBarButtonItem for a second time?

like image 577
Sam Spencer Avatar asked Nov 27 '11 16:11

Sam Spencer


People also ask

How do you dismiss a storyboard?

Select the button that should make the UIViewController Disappear and drag it to the UIViewController you want to go to. In my case it shows **dismiss Controller* because of the name of my Class. Select it and you are done!

How do you dismiss a popover in Swift?

It is an array of views in the interface behind the popover; the user can interact with these views, but a tap anywhere else outside the popover will dismiss it (with no effect on the thing tapped). If passThroughViews is nil, a tap anywhere outside the popover will dismiss it.


1 Answers

EDIT: These problems appear to be fixed as of iOS 7.1 / Xcode 5.1.1. (Possibly earlier, as I haven't been able to test all versions. Definitely after iOS 7.0, since I tested that one.) When you create a popover segue from a UIBarButtonItem, the segue makes sure that tapping the popover again hides the popover rather than showing a duplicate. It works right for the new UIPresentationController-based popover segues that Xcode 6 creates for iOS 8, too.

Since my solution may be of historical interest to those still supporting earlier iOS versions, I've left it below.


If you store a reference to the segue's popover controller, dismissing it before setting it to a new value on repeat invocations of prepareForSegue:sender:, all you avoid is the problem of getting multiple stacking popovers on repeated presses of the button -- you still can't use the button to dismiss the popover as the HIG recommends (and as seen in Apple's apps, etc.)

You can take advantage of ARC zeroing weak references for a simple solution, though:

1: Segue from the button

As of iOS 5, you couldn't make this work with a segue from a UIBarButtonItem, but you can on iOS 6 and later. (On iOS 5, you'd have to segue from the view controller itself, then have the button's action call performSegueWithIdentifier: after checking for the popover.)

2: Use a reference to the popover in -shouldPerformSegue...

@interface ViewController @property (weak) UIPopoverController *myPopover; @end  @implementation ViewController - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {     // if you have multiple segues, check segue.identifier     self.myPopover = [(UIStoryboardPopoverSegue *)segue popoverController]; } - (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {     if (self.myPopover) {         [self.myPopover dismissPopoverAnimated:YES];         return NO;     } else {         return YES;     } } @end 

3: There's no step three!

The nice thing about using a zeroing weak reference here is that once the popover controller is dismissed -- whether programmatically in shouldPerformSegueWithIdentifier:, or automatically by the user tapping somewhere else outside the popover -- the ivar goes to nil again, so we're back to our initial state.

Without zeroing weak references, we'd have to also:

  • set myPopover = nil when dismissing it in shouldPerformSegueWithIdentifier:, and
  • set ourself as the popover controller's delegate in order to catch popoverControllerDidDismissPopover: and also set myPopover = nil there (so we catch when the popover is automatically dismissed).
like image 117
rickster Avatar answered Oct 08 '22 11:10

rickster