Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Close button on adaptive popover

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 the nav controller to be present on both iPhone and iPad.

On an iPad, this works great in a popover.

On an iPhone, I get the modal presentation, so now I need an additional bar button item to dismiss the modal view. From watching the WWDC videos, I've attempted the following in the root view controller:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    UIViewController *vc = segue.destinationViewController;
    vc.popoverPresentationController.delegate = self;
}

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

- (UIViewController *)presentationController:(UIPresentationController *)controller viewControllerForAdaptivePresentationStyle:(UIModalPresentationStyle)style {
    UINavigationController *nvc = (UINavigationController *)controller.presentedViewController;
    UIBarButtonItem *bbi = [[UIBarButtonItem alloc] initWithTitle:@"Done" style:UIBarButtonItemStyleDone target:self action:@selector(dismissPopover)];
    nvc.topViewController.navigationItem.leftBarButtonItem = bbi;
    return nvc;
}

I understand the -presentationController:viewControllerForAdaptivePresentationStyle: method should only get called when the UI is adaptive i.e. modal, however it doesn't get called at all, even when running as a modal on iPhone.

like image 394
Nick Avatar asked Aug 12 '14 22:08

Nick


2 Answers

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)
    }

}
like image 190
Travis M. Avatar answered Nov 08 '22 04:11

Travis M.


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;
}
like image 12
Nick Avatar answered Nov 08 '22 04:11

Nick