How to create Popovers in iOS 9

Screen Shot 2015-10-18 at 08.55.13

iOS 8 introduced a new way of creating Popovers on iPad: instead of using the trusty old (nightmarish) UIPopoverController class, we can now use the UIPopoverPresentationController. It’s available on iPads as well as iPhones.

The great news for us developers is that we no longer have to check what type of device we’re on and present our content accordingly:  we simply present a Popover, and if we happen to be on an iPhone, iOS will automatically show our view controller as an action sheet instead. No other code change or if-then query is necessary.

While this approach was optional in iOS 8, the UIPopoverController is now deprecated and should no longer be used in iOS 9 (and good riddens I say). Both the creation as well as dismissal process have been streamlined, and I find it’s now actually a joy rather than a chore to play with Popovers.

Let’s see how to create a simple Popover Presentation Controller in iOS 9.

Popover Basics

The concept of a Popover is still the same: we need a content view controller that’s displayed inside the Popover, while the Popover itself is a different object (now called the Popover presentation controller). The view controller can have a custom class (for the UI and logic that’s presented therein), while the presentation controller does not require any additional code.

To react to the dismissal of a Popover we can implement a delegate. We’ll see how this works in a moment.

Presenting a Popover from a Bar Button Item

Popovers need an anchor, such as a UIBarButtonItem in a navigation controller. I have a reference to it, so this following Popover can point its arrow at it:

- (IBAction)barButtonLeft:(id)sender {

    // grab the view controller we want to show
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    UIViewController *controller = [storyboard instantiateViewControllerWithIdentifier:@"Pop"];
    
    // present the controller
    // on iPad, this will be a Popover
    // on iPhone, this will be an action sheet
    controller.modalPresentationStyle = UIModalPresentationPopover;
    [self presentViewController:controller animated:YES completion:nil];
    
    // configure the Popover presentation controller
    UIPopoverPresentationController *popController = [controller popoverPresentationController];
    popController.permittedArrowDirections = UIPopoverArrowDirectionAny;
    popController.barButtonItem = self.leftButton;
    popController.delegate = self;
}

Here’s what we do step by step: first we grab our content view controller. I’m instantiating one from my main storyboard. Make sure the Storyboard ID is set in Interface Builder so we can reference it. On iPhone, we could go ahead and present the view controller at this point, but because we want to be Popover-Compatible, we’ll do a little extra work.

Next I’m setting the modal presentation style of my view controller to UIModalPresentationPopover. This tells iOS that if possible, we want to see our content presented as a Popover. Now we show the view controller, even though it won’t be presented until we’re done with our next step.

We need to create a UIPopoverPresentationController object and configure it (i.e. instantiate with our content view controller, set the delegate, anchor point, etc). And that’s it: now iOS will show our content. If we’re on an iPad, it will now be shown in a Popover. If we’re on an iPhone or iPod it’ll show as an action sheet.

I know this looks weird: we seemingly present the view controller first, and then we configure the presentation controller. Even for Mr. Spock this would be highly illogical. However, according to the Apple documentation, UIKit will only ask for a presentation controller when the content view controller starts its presentation.

Try it out, it works like a charm!

Presenting a Popover from an arbitrary anchor point

We don’t need to use a bar button item as an anchor point. Instead we can use the sourceView and sourceRect properties of the presentation controller to set its origin:

- (IBAction)popoverWithoutBarButton:(id)sender {
    
    // grab the view controller we want to show
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    UIViewController *controller = [storyboard instantiateViewControllerWithIdentifier:@"Pop"];
    
    // present the controller
    // on iPad, this will be a Popover
    // on iPhone, this will be an action sheet
    controller.modalPresentationStyle = UIModalPresentationPopover;
    [self presentViewController:controller animated:YES completion:nil];
    
    // configure the Popover presentation controller
    UIPopoverPresentationController *popController = [controller popoverPresentationController];
    popController.permittedArrowDirections = UIPopoverArrowDirectionUp;
    popController.delegate = self;
    
    // in case we don't have a bar button as reference
    popController.sourceView = self.view;
    popController.sourceRect = CGRectMake(30, 50, 10, 10);
}

Experiment with the rectangle values to find the correct placement for Popovers presented this way. The first two describe x/y coordinates for where the top left corner of the Popover starts, while the last two describe the size of the frame around the Popover (I think).

Dismissing a Popover

When a user taps on the outside of our new Popover, it is dismissed automatically – if we wish for this to happen. No additional work on our part is needed to make that happen.

What was slightly tricky with the old UIPopoverController class was to dismiss a Popover as a result of an action inside the content view controller. Lucky for us this is now super easy: simply call the content view controller’s dismissViewcontroller method, and the Popover is dismissed without crashes! Hurra!

In your custom content view controller class, you may have code like this. Call it when you want the Popover to go away:

- (void)dismissMe {
    
    [self dismissViewControllerAnimated:YES completion:nil];
}

Overall this is now just a modal presentation with a different style. Therefore, exchanging data is as easy as it is with non-Popover presentations.

Reacting to Popover Dismissal

There’s a new protocol in town called the UIPopoverPresentationControllerDelegate that is called upon dismissal and position change due to rotation or interface changes. We can even prevent a Popover from being dismissed if we wish. Here are the three methods we can implement:

# pragma mark - Popover Presentation Controller Delegate

- (void)popoverPresentationControllerDidDismissPopover:(UIPopoverPresentationController *)popoverPresentationController {
    
    // called when a Popover is dismissed
}

- (BOOL)popoverPresentationControllerShouldDismissPopover:(UIPopoverPresentationController *)popoverPresentationController {
    
    // return YES if the Popover should be dismissed
    // return NO if the Popover should not be dismissed
    return YES;
}

- (void)popoverPresentationController:(UIPopoverPresentationController *)popoverPresentationController willRepositionPopoverToRect:(inout CGRect *)rect inView:(inout UIView *__autoreleasing  _Nonnull *)view {
    
    // called when the Popover changes position
}

Don’t forget to conform to the protocol, and set the delegate to your reacting class:

@interface ViewController ()

Demo Project

I have a simple demo project that shows all these things in action:

Further Reading





11 thoughts on “How to create Popovers in iOS 9

  1. “While this approach was optional in iOS 8, the UIPopoverController is now deprecated and can no longer be used in iOS 9”
    Uhhh, yes it can. I’m using it just fine.

    1. Hi Alvaro, there’s no magic formula for state preservation with Popovers, like there is with table views for example. If you need this functionality, your only option is to save the state of each UI element manually, and bring it back when the Popover is needed again.

    1. Hi Robert, sadly you cannot use the UIPopoverPresentationController in iOS 7, it was only introduced in iOS 8. If your app needs to support iOS 7 you need to use the old UIPopoverController approach.

  2. First, thanks for this post, it help me a lot.
    In second, i have found a key to do the initialization of popup in some different way. The key problem is that “popoverPresentationController” property of ViewController in popup will not be initialized until “modalPresentationStyle” property of that controller not set to UIModalPresentationPopover (.Popover in swift). Here is my working code in swift, as you see, presentViewController goes after all setup done:

    func popupDishDetails(dishIndex: Int, borderWidth: CGFloat) {
    let view = self.dishDetailsView as! DishDetailViewController //this was instantiated from storyboard earlier in class setup
    let newBounds = UIScreen.mainScreen().bounds.insetBy(dx: borderWidth, dy: borderWidth) // creating bounds rectangle with passed border size
    view.preferredContentSize = CGSize(width: newBounds.width, height: newBounds.height) // setup size of popup window
    view.item = self.dishesData[dishIndex] // here I pass some data to display in popup
    view.modalPresentationStyle = .Popover // this is the key – if you don’do that, popoverPresentationController property would not be initialised when you acces it in next step
    let popController = view.popoverPresentationController // initialising popoverPresentationController by accessing it
    popController?.permittedArrowDirections = .Any // do remaining setup
    popController?.sourceView = self.view
    popController?.delegate = self
    popController?.sourceRect = CGRect(
    x: self.view.bounds.origin.x+10,
    y: self.view.bounds.origin.y+10,
    width: 1,
    height: 1)
    self.presentViewController(view, animated: true, completion:nil) // presenting popup

    }

  3. Hi Jay. Great Post!
    I’m trying use popoverPresentationController with UIDatePicker but the datepicker don’t show.
    You have any tip to use datepicker with popoverPresentationController?

Leave a Reply