I am updating my app for iOS 13’s new “card-style” modal views. All has been working well using UIAdaptivePresentationControllerDelegate
’s presentationControllerDidAttemptToDismiss()
and presentationControllerDidDismiss()
functions. But, for views that have their .modalPresentationStyle
set to .popover
, presentationControllerDidDismiss()
is not called when being presented in compact environments (such as a phone or iPad in split or slide-over). It’s called correctly when presented in a regular size class environment (such as an iPad full-screen).
My code setting this up is pretty straightforward:
The code presenting the popover:
func showChooser() {
// other setup code...
navController.modalPresentationStyle = .popover
navController.popoverPresentationController?.barButtonItem = self.viewController?.navigationItem.leftBarButtonItem
self.present(navController, animated: true)
}
Then, the presented controller conforms to UIAdaptivePresentationControllerDelegate
and sets up:
// This is in the presented view controller (i.e. the popover)
override func viewDidLoad() {
// other setup removed for brevity…
self.navigationController?.presentationController?.delegate = self
}
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
print("did dismiss")
self.cancel?()
}
When the view is presented in regular size-class environments, it is displayed correctly as a popover. When the user taps outside the popover, then presentationControllerDidDismiss()
is called. However, when the same code is presented in a compact environment, it’s displayed correctly (as a card style), but when the user drags down the view, presentationControllerDidDismiss()
is not called.
If I change the .modalPresentationStyle
to something else such as .pageSheet
or .formSheet
, then it all works as expected in either compact or regular presentations.
I’ve tried using the delegate's adaptivePresentationStyle()
to change the style to .formSheet
on compact environments, but presentationControllerDidDismiss()
is still not called correctly.
Update:
I should have mentioned that my current workaround is to check the size class and change .modalPresentationStyle
as needed:
if self.traitCollection.horizontalSizeClass == .compact {
navController.modalPresentationStyle = .automatic
} else {
navController.modalPresentationStyle = .popover
navController.popoverPresentationController?.barButtonItem = self.viewController?.navigationItem.leftBarButtonItem
}
This works, but it seems that just using the .popover
style should adapt properly and call the correct delegate methods.
Update 2: I've updated the code above to clarify that the presented view controller is the one handling the delegate methods.
Also, after digging into this more, I noticed that if the presenting view controller is the delegate and handles the delegate methods, then this all works as expected. Since it also works in the presented view controller for all .modalPresentationStyle
's except popover in compact environments, perhaps there is some lifetime issue when popovers are presented in that way?
Any ideas about what I might be doing wrong?
The problem is merely one of timing. You're doing this in the second view controller:
override func viewDidLoad() {
self.navigationController?.presentationController?.delegate = self
}
That's too late. You can set the delegate where you configure and perform the presentation:
func showChooser() {
navController.modalPresentationStyle = .popover
navController.popoverPresentationController?.barButtonItem =
self.viewController?.navigationItem.leftBarButtonItem
navController.presentationController?.delegate = // *
navController.viewControllers[0]
as! UIAdaptivePresentationControllerDelegate
self.present(navController, animated: true)
}
If you prefer to insist upon having the second view controller set itself as delegate, do it earlier. The first good opportunity is willMove
:
override func willMove(toParent parent: UIViewController?) {
self.parent?.presentationController?.delegate = self
}
Thanks heaps for that example - just to extend on the details from Matt and for the benefit of those looking for a generic example (with the creation of a standalone viewController), I believe something like the below should also work:
func presentExampleViewController() {
// Any other setup code specific to the view in your app can go here...
let exampleViewController = SomeCustomViewController()
exampleViewController.presentationController?.delegate = exampleViewController
self.present(exampleViewController, animated: true)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With