How to remove Popover storyboards

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

Xcode 5.0 Connections Inspector with Popover

The popover view works fine. However, I cannot make the popover disappear when I touch the UIBarButtonItem that launched it.

When you press the button (first time), a pop-up window 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 keep pressing the button). According to the iOS Human Interface Guide, I need the popover to appear on the first touch and disappear on the second:

Make sure that only one popover is displayed on the screen at a time. You should not display more than one popover (or a custom view designed to view and behave like a popover) at the same time. In particular, you should avoid displaying a cascade or hierarchy of popovers at the same time, in which one popover appears from another.

How can I reject a popover when a user removes a UIBarButtonItem second time?

+72
ios objective-c uistoryboard uibarbuttonitem uipopovercontroller
Nov 27 '11 at 16:57
source share
6 answers

EDIT: These issues appear to be fixed with iOS 7.1 / Xcode 5.1.1. (Perhaps earlier, since I could not test all versions. Definitely after iOS 7.0, since I tested this one.) When you create a popover segment from UIBarButtonItem , segue makes sure that clicking on popover again hides instead of showing a duplicate. It works correctly for the new pop UIPresentationController segments that Xcode 6 creates for iOS 8.

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




If you keep a reference to the segue popover controller, discarding it before setting it to a new value when you call prepareForSegue:sender: again, all you avoid is the problem of getting multiple flowing popovers when you click the button again - you still don’t you can use the button to reject the popover, as recommended by HIG (and, as seen from Apple apps, etc.).

You can use ARC to nullify weak links for a simple solution:

1: Segue from the button

As in iOS 5, you could not do this work with segue with UIBarButtonItem , but you can on iOS 6 and later. (On iOS 5, you will have to switch from the view controller itself, then after calling the performSegueWithIdentifier: button performSegueWithIdentifier: call the button call

)

2: Use the popover link 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 is no third step!

The good idea of ​​using a null reference here is that after the popover controller is fired - whether it is programmatically in shouldPerformSegueWithIdentifier: or automatically when the user taps somewhere else outside the popover - ivar switches to nil again, so we will return to our initial state.

Without zeroing out weak links, we also need:

  • set myPopover = nil , rejecting it in shouldPerformSegueWithIdentifier: and
  • set yourself as the delegate of the popover controller to catch popoverControllerDidDismissPopover: and also set myPopover = nil there (so that we will catch when the popover is automatically rejected).
+114
Apr 20 '12 at 0:10
source share

I have found a solution here https://stackoverflow.com/a/167444/ ... In the first prepareForSegue file: sender: save the pointer to the UIPopoverController and the user whose pointer rejects the popover in subsequent calls in ivar / property.

 ... @property (nonatomic, weak) UIPopoverController* storePopover; ... - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([segue.identifier isEqualToString:@"My segue"]) { // setup segue here [self.storePopover dismissPopoverAnimated:YES]; self.storePopover = ((UIStoryboardPopoverSegue*)segue).popoverController; ... } 
+13
Nov 28 '11 at 19:51
source share

I used custom segue for this.

one

create custom segue for use in storyboard:

 @implementation CustomPopoverSegue -(void)perform { // "onwer" of popover - it needs to use "strong" reference to retain UIPopoverReference ToolbarSearchViewController *source = self.sourceViewController; UIViewController *destination = self.destinationViewController; // create UIPopoverController UIPopoverController *popoverController = [[UIPopoverController alloc] initWithContentViewController:destination]; // source is delegate and owner of popover popoverController.delegate = source; popoverController.passthroughViews = [NSArray arrayWithObject:source.searchBar]; source.recentSearchesPopoverController = popoverController; // present popover [popoverController presentPopoverFromRect:source.searchBar.bounds inView:source.searchBar permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES]; } @end 

2

in the form of a controller, which is the source / input of segue, for example. start with the action:

 -(void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar { if(nil == self.recentSearchesPopoverController) { NSString *identifier = NSStringFromClass([CustomPopoverSegue class]); [self performSegueWithIdentifier:identifier sender:self]; } } 

3

links are assigned to the segue that creates the UIPopoverController - when popover is rejected

 -(void)searchBarTextDidEndEditing:(UISearchBar *)searchBar { if(self.recentSearchesPopoverController) { [self.recentSearchesPopoverController dismissPopoverAnimated:YES]; self.recentSearchesPopoverController = nil; } } 

Regards, Peter

+2
Mar 11 2018-12-12T00:
source share

I decided to create a custom ixPopoverBarButtonItem that either starts segue or rejects the displayed popover.

What I do: I switch the action and purpose of the button, so it either starts a session or deletes the currently displayed popover.

I needed a lot of search queries for this solution, I do not want to take loans for the idea of ​​switching actions. Entering code in a custom button was my approach to keep the template code in my view to a minimum.

In the storyboard, I define the BarButtonItem class to my custom class:

custom bar button

Then I pass the popover created by segue to my custom button implementation in the prepareForSegue:sender: method:

 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([segue.identifier isEqualToString:@"myPopoverSegue"]) { UIStoryboardPopoverSegue* popSegue = (UIStoryboardPopoverSegue*)segue; [(ixPopoverBarButtonItem *)sender showingPopover:popSegue.popoverController]; } } 

Btw ... since I have several buttons that launch popovers, I still need to keep a link to the currently displayed popover and reject it when I make a new one visible, but that wasn’t your question ...

This is how I applied my custom UIBarButtonItem:

... interface:

 @interface ixPopoverBarButtonItem : UIBarButtonItem - (void) showingPopover: (UIPopoverController *)popoverController; @end 

... and impl:

 #import "ixPopoverBarButtonItem.h" @interface ixPopoverBarButtonItem () @property (strong, nonatomic) UIPopoverController *popoverController; @property (nonatomic) SEL tempAction; @property (nonatomic,assign) id tempTarget; - (void) dismissPopover; @end @implementation ixPopoverBarButtonItem @synthesize popoverController = _popoverController; @synthesize tempAction = _tempAction; @synthesize tempTarget = _tempTarget; -(void)showingPopover:(UIPopoverController *)popoverController { self.popoverController = popoverController; self.tempAction = self.action; self.tempTarget = self.target; self.action = @selector(dismissPopover); self.target = self; } -(void)dismissPopover { [self.popoverController dismissPopoverAnimated:YES]; self.action = self.tempAction; self.target = self.tempTarget; self.popoverController = nil; self.tempAction = nil; self.tempTarget = nil; } @end 

ps: I'm new to ARC, so I'm not quite sure what I'm missing here. Please tell me if I ...

+2
Apr 05 2018-12-12T00:
source share

I solved this problem without having to keep a copy of the UIPopoverController . Just process everything in the storyboard (Toolbar, BarButtons, etc.) And

  • handles the visibility of a popover with a boolean,
  • make sure there is a delegate and it is set to self

Here is the whole code:

ViewController.h

 @interface ViewController : UIViewController <UIPopoverControllerDelegate> @end 

ViewController.m

 @interface ViewController () @property BOOL isPopoverVisible; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.isPopoverVisible = NO; } - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // add validations here... self.isPopoverVisible = YES; [[(UIStoryboardPopoverSegue*)segue popoverController] setDelegate:self]; } - (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender { return !self.isPopoverVisible; } - (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController { self.isPopoverVisible = NO; } @end 
+2
Nov 19 '13 at 14:03
source share

I took the rikster answer and packaged it into a class derived from the UIViewController. This solution requires the following:

  • iOS 6 (or later) with ARC
  • Derive a view controller from this class
  • be sure to call the "super" version of prepareForSegue: sender and shouldPerformSegueWithIdentifier: sender if you override these methods.
  • Use named popover segue

The best part is that you don’t need to do any special encodings to support the proper handling of Popovers.

Interface

 @interface FLStoryboardViewController : UIViewController { __strong NSString *m_segueIdentifier; __weak UIPopoverController *m_popoverController; } - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender; - (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender; @end 

Implementation

 @implementation FLStoryboardViewController - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if( [segue isKindOfClass:[UIStoryboardPopoverSegue class]] ) { UIStoryboardPopoverSegue *popoverSegue = (id)segue; if( m_popoverController == nil ) { assert( popoverSegue.identifier.length > 0 ); // The Popover segue should be named for this to work fully m_segueIdentifier = popoverSegue.identifier; m_popoverController = popoverSegue.popoverController; } else { [m_popoverController dismissPopoverAnimated:YES]; m_segueIdentifier = nil; m_popoverController = nil; } } else { [super prepareForSegue:segue sender:sender]; } } - (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender { // If this is an unnamed segue go ahead and allow it if( identifier.length != 0 ) { if( [identifier compare:m_segueIdentifier] == NSOrderedSame ) { if( m_popoverController == NULL ) { m_segueIdentifier = nil; return YES; } else { [m_popoverController dismissPopoverAnimated:YES]; m_segueIdentifier = nil; m_popoverController = nil; return NO; } } } return [super shouldPerformSegueWithIdentifier:identifier sender:sender]; } @end 

Source available on github

+1
Nov 03 '12 at 13:35
source share



All Articles