How to create Popovers in iOS 9

来源:互联网 时间:1970-01-01

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 nowdeprecated and can 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 showUIStoryboard *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 sheetcontroller.modalPresentationStyle = UIModalPresentationPopover;[self presentViewController:controller animated:YES completion:nil];// configure the Popover presentation controllerUIPopoverPresentationController *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 showthe view controller, even though it won’t be presentedup until we’re done with ournext 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 froman 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 showUIStoryboard *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 sheetcontroller.modalPresentationStyle = UIModalPresentationPopover;[self presentViewController:controller animated:YES completion:nil];// configure the Popover presentation controllerUIPopoverPresentationController *popController = [controller popoverPresentationController];popController.permittedArrowDirections = UIPopoverArrowDirectionUp;popController.delegate = self;// in case we don't have a bar button as referencepopController.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 dismissedreturn 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 () <UIPopoverPresentationControllerDelegate> Demo Project

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

https://github.com/versluis/Popovers-2015 Further Reading


相关阅读:
Top