Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS 13's presentationControllerDidDismiss() Not Called for Popover in Compact Environment

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?

like image 559
coping Avatar asked Dec 03 '19 01:12

coping


2 Answers

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
}
like image 138
matt Avatar answered Sep 21 '22 05:09

matt


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)
}
like image 32
michaelrbg Avatar answered Sep 23 '22 05:09

michaelrbg