16
votes

I have a view controller that's presented in a popover using a storyboard segue.

enter image description here

In the presenting view controller, I had the following code:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if let svc = segue.destinationViewController as? SettingsViewController {
        svc.popoverPresentationController?.delegate = self
    }
}

However, it turns out that the presented view controller, even though it appears as a popover, has a modalPresentationStyle of '.Modal, and hence a nil popoverPresentationController. Weird!

So, I updated the code as follows:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if let svc = segue.destinationViewController as? SettingsViewController {
        svc.modalPresentationStyle = .Popover
        svc.popoverPresentationController?.delegate = self
    }
}

The svc.popoverPresentationController delegate is now set OK, but if the popover is dismissed by the user tapping outside, none of the UIPopoverPresentationControllerDelegate delegate methods (e.g. popoverPresentationControllerShouldDismissPopover are called. What am I missing?

2
Probably a stupid question, are you sure the segue is set as "Present as Popover" in the storyboard?Fabio Ritrovato
Yes, definitely - and it presents correctly as a popover (and did even before setting svc.modalPresentationStyle = .Popover). Worth checking though!Ashley Mills
One thing is weird, it should say "Present as Popover", as just "Popover" should be deprecated, but i don't think that's the issue...Fabio Ritrovato
"Present as Popover" is an option when size classes are enabled. Without that, it's just "Popover"Ashley Mills
is svc.popoverPresentationController non-nil at that time? Else, it would silently not set the delegate.Tobi Nary

2 Answers

7
votes

No need for delegation in this case. If the presentingViewController (whatever vc is containing the popover) just overrides:

Swift 4

override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
    print("Dismiss: \(String(describing: self.presentedViewController))")
    super.dismiss(animated: flag, completion: completion)
}

Swift 3

override func dismissViewControllerAnimated(flag: Bool, completion: (() -> Void)?) {
    // Before calling super get a handle on which controller is being dismissed
    print("Dismiss: \(self.presentedViewController)")
    super.dismissViewControllerAnimated(flag, completion: completion)
}

You will get notified no matter how it is dismissed. You also do not need to set any additional variables/settings in the prepareForSegue: (at least to handle this interaction).

0
votes

Ran into the same issue and after reading through the documentation, I realized that you need to call:

[self presentViewController:myPopoverViewController animated: YES completion: nil];

in order for the delegate methods to get called.

The full snippet is as follows and is run within my -(void)prepareForSegue:sender method:

// Present the view controller using the popover style.
myPopoverViewController.modalPresentationStyle = UIModalPresentationPopover;
[self presentViewController:myPopoverViewController animated: YES completion: nil];

// Get the popover presentation controller and configure it.
UIPopoverPresentationController *presentationController =
         [myPopoverViewController popoverPresentationController];
presentationController.permittedArrowDirections =
         UIPopoverArrowDirectionLeft | UIPopoverArrowDirectionRight;
presentationController.sourceView = myView;
presentationController.sourceRect = sourceRect;

https://developer.apple.com/documentation/uikit/uipopoverpresentationcontroller