6

Before presenting a view controller I set the modalPresentationStyle property to UIModalPresentationPopover. This will present the view controller as a popover when running on devices with a regular horizontal size class (iPad and iPhone 6+ in landscape) and as modal/fullscreen on other devices. It's also possible to override this behaviour by overriding adaptivePresentationStyleForPresentationController so that the view controller is presented as a popover on all devices.

I wonder if it's possible, after a view controller is presented, to know if it's presented as a popover or not? Just looking at the size class won't do it as it's possible that the view controller overrides adaptivePresentationStyleForPresentationController.

The obvious answer would be that I, as the programmer, should know if I override adaptivePresentationStyleForPresentationController or not but I want to write a function that can determine this in runtime for any view controller by passing in the view controller or maybe the UIPopoverPresentationController (or any other object needed) as an argument.

Here's some code to present the view controller:

navigationController = (UINavigationController *)[MVSStore sharedInstance].addViewController;
navigationController.modalPresentationStyle = UIModalPresentationPopover;
[self presentViewController:navigationController animated:YES completion:^{}];

UIPopoverPresentationController *popoverController = navigationController.popoverPresentationController;
popoverController.sourceView = self.view;
popoverController.sourceRect = CGRectMake(20, 20, 20, 20); // Just a dummy
popoverController.permittedArrowDirections = UIPopoverArrowDirectionAny;

Here's the current code to detect if the view controller is presented as a popover or not. But as mentioned above it just looks at the size class which doesn't work for all cases.

+ (BOOL)willPresentTruePopover:(id<UITraitEnvironment>)vc {
    return ([vc traitCollection].horizontalSizeClass == UIUserInterfaceSizeClassRegular);
}

I cannot find any property or function in UIViewController or UIPopoverPresentationController (or anywhere else) that gives me this information right away but maybe I'm missing something?

Markus
  • 767
  • 2
  • 7
  • 19
  • Just curious, why do you want to know that? – Khanh Nguyen Sep 14 '15 at 11:10
  • I would use it to show or hide a cancel button. In case of a popover I don't need to show a cancel button as the view controller is dismissed by tapping outside of it. Another reason is to update the parent view controller when the view controller is dismissed. This is currently done in the parent's viewWillAppear and viewDidAppear. viewDidAppear and viewWillAppear are however not called when the child view controller is presented as a popover (but they are when modal) so I need to handle this special case somehow. – Markus Sep 14 '15 at 11:16
  • @Markus I added an answer that shows how to only add a cancel button when it is needed. – malhal Mar 01 '16 at 14:40
  • Check this post: http://stackoverflow.com/questions/26687017/ios-8-presentationcontroller-determine-if-really-is-popover/37180653#37180653 – Slyv May 12 '16 at 08:04

3 Answers3

3

You said you were trying to do this to remove the cancel/done button. Instead, only add the button in the case when it is needed.

The official way to implement this is first remove the Done button from your view controller and second, when adapting to compact embed your view controller in a navigation controller, now add the done button as a navigation item:

func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
    return UIModalPresentationStyle.FullScreen
}

func presentationController(controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
    let navigationController = UINavigationController(rootViewController: controller.presentedViewController)
    let btnDone = UIBarButtonItem(title: "Done", style: .Done, target: self, action: "dismiss")
    navigationController.topViewController.navigationItem.rightBarButtonItem = btnDone
    return navigationController
}

func dismiss() {
    self.dismissViewControllerAnimated(true, completion: nil)
}

Full Tutorial

Screenshots

malhal
  • 26,330
  • 7
  • 115
  • 133
2

Use the UIAdaptivePresentationControllerDelegate method presentationController:willPresentWithAdaptiveStyle:transitionCoordinator:. To query the presentation style at other times, ask the presentation controller for its adaptivePresentationStyleForTraitCollection:, passing the current traits. These methods were added in iOS 8.3, and are not documented yet.

Douglas Hill
  • 1,537
  • 10
  • 14
  • I didn't manage to set the delegate accordingly to get this to work. Setting the view controller class to conform to UIAdaptivePresentationControllerDelegate and then use popoverController.delegate = self; compiles but I get a warning that self doesn't conform to UIPopoverPresentationControllerDelegate and willPresent... function is not called. If vc conforms to UIPopoverPresentationControllerDelegate instead it compiles but function is not called as UIPopoverPresentationControllerDelegate does not expose this function (even if it conforms to UIAdaptivePresentationControllerDelegate itself.) – Markus Sep 17 '15 at 18:11
  • Well, I managed to get it to work. It's actually working correctly but I'm not very confident in using undocumented features... I've heard of people struggling with the AppStore review time about these things. I would also rather find a solution that works from iOS 8.0. – Markus Sep 17 '15 at 20:38
  • Oh, these are undocumented because they didn’t get around to documenting them yet. These methods are present in public headers, and are perfectly fine for App Store review. – Douglas Hill Sep 18 '15 at 18:42
2

I'm attacking it in the same way you were, in that I set up the Done button with its target-action in Interface Builder. In order to remove it, I was testing if the popoverPresentationController != nil. On my testing device (iPhone 5 running iOS 10), this test successfully ignored the iPhone while executing on an iPad Pro running iOS 11. I ran into problems while testing it on an iPhone 8 running iOS 11. It appears in iOS 11 that a popoverPresentationController is now instantiated even when presenting the view modally. As a result, I am just testing the horizontal size class of the presenting view controller. Not sure if that's the correct way, but it works for me, since I can't find any way for the popoverPresentationController to tell me it is actually presenting modally.

    weak var ppcDelegate: UIPopoverPresentationControllerDelegate?

...

    if popoverPresentationController != nil && 
        popoverPresentationController!.presentingViewController.traitCollection.horizontalSizeClass == .regular {

        navigationItem.rightBarButtonItem = nil
        popoverPresentationController?.delegate = ppcDelegate
    }
Dan M.
  • 51
  • 4