In a storyboard I have a root view controller with a button which triggers a \'Present as Popover\' segue to a UINavigationController containing a UITableViewController. I want
Ok, I have managed to get it working. I think my problem was that the popoverPresentationController
property traverses up the view controller heirarchy until it finds a view controller with a popoverPresentationController
, i.e. if I have a view controller inside a navigation controller inside a popover the view controller popoverPresentationController
would go to the nav controller and use it's property. For this to work, the view controller has to be a child of the navigation controller. At all the points I was trying to use the popoverPresentationController
, this was not the case, e.g init
, viewDidLoad
, viewWillAppear
. For some reason, willMoveToParentViewController
is not called, even though didMove
does get called. So I have no idea how to reference popoverPresentationController
in a vc inside a nav controller before the ppc delegate methods are called.
However, you can reference it in the presenting view controller in prepareForSegue
, but you do need to explicitly tell it what presentation style to use. Here's my code that works when placed in the presenting view controller:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
UIViewController *dest = segue.destinationViewController;
dest.popoverPresentationController.delegate = self;
}
- (void)dismiss {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller {
return UIModalPresentationFullScreen; // required, otherwise delegate method below is never called.
}
- (UIViewController *)presentationController:(UIPresentationController *)controller viewControllerForAdaptivePresentationStyle:(UIModalPresentationStyle)style {
// If you don't want a nav controller when it's a popover, don't use one in the storyboard and instead return a nav controller here
UIBarButtonItem *bbi = [[UIBarButtonItem alloc] initWithTitle:@"Done" style:UIBarButtonItemStyleDone target:self action:@selector(dismiss)];
UINavigationController *nc = (UINavigationController *)controller.presentedViewController;
nc.topViewController.navigationItem.leftBarButtonItem = bbi;
return controller.presentedViewController;
}
I found that the accepted answer doesn't correctly display a "done" button when in Compact mode (e.g. iPhone) but remain as popover in Regular mode (e.g. iPad).
The following code is the bare minimum to make this work – place these in the presenting view controller class.
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
UIViewController *dest = segue.destinationViewController;
dest.popoverPresentationController.delegate = self;
}
- (UIViewController *)presentationController:(UIPresentationController *)controller viewControllerForAdaptivePresentationStyle:(UIModalPresentationStyle)style {
UIViewController* presentedViewController = controller.presentedViewController;
if ([controller isKindOfClass:[UIPopoverPresentationController class]] && style == UIModalPresentationFullScreen) {
UINavigationController* navCtrl = [[UINavigationController alloc] initWithRootViewController:presentedViewController];
UIBarButtonItem *bbi = [[UIBarButtonItem alloc] initWithTitle:@"Done" style:UIBarButtonItemStyleDone target:self action:@selector(dismiss:)];
presentedViewController.navigationItem.rightBarButtonItem = bbi;
return navCtrl;
}
return presentedViewController;
}
-(IBAction)dismiss:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
Notice the three differences with the accepted answer:
adaptivePresentationStyleForPresentationController:
Here's the Swift version of Nick's correct answer for those who want a quick cut and paste.
Note: This is to create a popover on your iPad but a modal sheet with a close button on your iPhone.
In Xcode 6.3 storyboard, you hook up a view controller and designate the segue as a "Present as Popover"
The below code should go in the view controller that segues to the popover, not in the popover itself:
First you set up the popover delegate:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "myPopoverSegueName" {
segue.destination.popoverPresentationController?.delegate = self
return
}
}
Then you add the delegate extension and create the navigation controller / close button on the fly:
extension myViewController: UIPopoverPresentationControllerDelegate {
func presentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
let btnDone = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(self.dismissPopover))
let nav = UINavigationController(rootViewController: controller.presentedViewController)
nav.topViewController?.navigationItem.leftBarButtonItem = btnDone
return nav
}
@objc private func dismissPopover() {
dismiss(animated: true, completion: nil)
}
}