7

I'd like to detect a modal dismissal in the view controller that's presenting the modal.

This method works amazing for detecting the new iOS 13 swipe dismissal on the new card modals:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "MyIdentifier" {
        segue.destination.presentationController?.delegate = self
    }
}

extension MyController: UIAdaptivePresentationControllerDelegate {    
    func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
        //clean up UI (de-selecting stuff) once modal has been dismissed
    }
}

However, presentationControllerDidDismiss is NOT called if the modal dismisses itself programmatically through an action:

@IBAction func btnDismissTap(_ sender: Any) {
    self.dismiss(animated: true, completion: nil)
}

Is this a bug or is there a way I can programmatically call whatever the "swipe" dismiss is so I can detect all dismissals the same way? Currently I'm writing extra "dismiss" delegate methods into my modals as a work around and it seems unnecessary.

William T.
  • 12,831
  • 4
  • 56
  • 53

3 Answers3

12

However, presentationControllerDidDismiss is NOT called if the modal dismisses itself programmatically through an action

 self.dismiss(animated: true, completion: nil)

It doesn’t need to be called, because you dismissed the modal yourself, in code. You cannot not know that the modal was dismissed. You don’t need to receive a dismissal signal, because you gave the dismissal signal in the first place.

You typically don’t get a delegate method call reporting something your own code did. Delegate methods report user actions. It would be crazy if everything you yourself did in code came back as a delegate method call.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • I gave the dismissal signal in another view controller (the modal), the view controller that presented the modal has no idea it was dismissed. It seems similar how swipe to dismiss works (action taken is on Modal, but it lets the presenting view controller know). – William T. Sep 15 '19 at 07:17
  • Yes, but that’s a completely different matter. It has nothing to do with swiping. You’d have that same issue with a form sheet on iPad, for example, even if swipe to dismiss had never been invented. – matt Sep 15 '19 at 07:36
  • 1
    See for example my discussion here https://stackoverflow.com/questions/54602662/unified-uiviewcontroller-became-frontmost-detection on the general problem of learning that a presented view controller was dismissed. – matt Sep 15 '19 at 07:39
  • Thanks, I guess I got confused from the name "presentationControllerDidDismiss", but really it only fires on certain types of dismissals and not others. I'll check out your other thread. – William T. Sep 15 '19 at 07:41
  • “I guess I got confused from the name” Yes, and that is what my answer explains. You typically don’t get a delegate method call reporting something your own code did. Delegate methods report user actions. It would be crazy if everything you yourself did in code came back as a delegate method call. – matt Sep 15 '19 at 07:44
9

Mojtaba Hosseini, answer is something I was looking for.

Currently, I need to write a delegate function to let the presenting view know that the user dismissed the modal PLUS do the presentationControllerDidDismiss handler for swipe dismissals:

@IBAction func btnDismissTap(_ sender: Any) {
    self.dismiss(animated: true, completion: {
        self.delegate?.myModalViewDidDismiss()
    })
}

I wanted to handle both of these the same way and Mojtaba's answer works for me. However, presentationControllerDidDismiss does not get invoked if you call it inside of the self.dismiss completion block, you need to call it before.

I adapted my code to use "presentationControllerWillDismiss" (for clarity) and simply called the delegate before I dismiss programmatically in my modals and it works great.

@IBAction func btnDismissTap(_ sender: Any) {
    if let pvc = self.presentationController {
        pvc.delegate?.presentationControllerWillDismiss?(pvc)
    }
    self.dismiss(animated: true, completion: nil)
}

Now, I no longer need to create delegate functions to handle modal dismissals in code and my swipe handler takes care of all scenarios.

FYI, what I'm "handling" is doing some UI clean up (de-selections, etc) on the presenting UI once the modal is dismissed.

William T.
  • 12,831
  • 4
  • 56
  • 53
  • 1
    if let pvc = self.presentationController { pvc.delegate?.presentationControllerDidDismiss?(pvc) } // it should be DidDismiss rather than WillDismiss – Amit Verma Sep 22 '19 at 20:07
  • The modal hasn't been dismissed yet, that happens on the next line. – William T. Sep 24 '19 at 03:04
  • 1
    @WilliamT. Since presentationControllerWillDismiss() can be called multiple times before presentationControllerDidDismiss() is called. And someone may want do sth here for other reason. In your case, you trigger the dismissal delegate yourself and you are pretty sure that the vc is about to be dismissed, thus I agree with AmitVerma's suggestion. Do not mess with willDissmiss and didDismiss. They are representing different situations. – Michael Revlis May 27 '21 at 05:59
8

As @matt mentioned, there is no need to inform the one who dismissed a view by delegate. Because it is already known. BUT if you need that delegate method to be called, you should call it manually your self after dismissing the view:

@IBAction func btnDismissTap(_ sender: Any) {
    self.dismiss(animated: true) {
        presentationController?.delegate?.presentationControllerDidDismiss?(presentationController!)
    }
}
Mojtaba Hosseini
  • 95,414
  • 31
  • 268
  • 278