Detecting sheet was dismissed on iOS 13

IosSwiftUiviewcontrollerUikitIos13

Ios Problem Overview


Before iOS 13, presented view controllers used to cover the entire screen. And, when dismissed, the parent view controller viewDidAppear function were executed.

Now iOS 13 will present view controllers as a sheet as default, which means the card will partially cover the underlying view controller, which means that viewDidAppear will not be called, because the parent view controller has never actually disappeared.

Is there a way to detect that the presented view controller sheet was dismissed? Some other function I can override in the parent view controller rather than using some sort of delegate?

Ios Solutions


Solution 1 - Ios

> Is there a way to detect that the presented view controller sheet was dismissed?

Yes.

> Some other function I can override in the parent view controller rather than using some sort of delegate?

No. "Some sort of delegate" is how you do it. Make yourself the presentation controller's delegate and override presentationControllerDidDismiss(_:).

https://developer.apple.com/documentation/uikit/uiadaptivepresentationcontrollerdelegate/3229889-presentationcontrollerdiddismiss


The lack of a general runtime-generated event informing you that a presented view controller, whether fullscreen or not, has been dismissed, is indeed troublesome; but it's not a new issue, because there have always been non-fullscreen presented view controllers. It's just that now (in iOS 13) there are more of them! I devote a separate question-and-answer to this topic elsewhere: https://stackoverflow.com/questions/54602662/unified-uiviewcontroller-became-frontmost-detection.

Solution 2 - Ios

Here's a code example of a parent view-controller which is notified when the child view-controller it presents as a sheet (i.e., in the default iOS 13 manner) is dismissed:

public final class Parent: UIViewController, UIAdaptivePresentationControllerDelegate
{
  // This is assuming that the segue is a storyboard segue; 
  // if you're manually presenting, just set the delegate there.
  public override func prepare(for segue: UIStoryboardSegue, sender: Any?)
  {
    if segue.identifier == "mySegue" {
      segue.destination.presentationController?.delegate = self;
    }
  }

  public func presentationControllerDidDismiss(
    _ presentationController: UIPresentationController)
  {
    // Only called when the sheet is dismissed by DRAGGING.
    // You'll need something extra if you call .dismiss() on the child.
    // (I found that overriding dismiss in the child and calling
    // presentationController.delegate?.presentationControllerDidDismiss
    // works well).
  }
}

Jerland2's answer is confused, since (a) the original questioner wanted to get a function call when the sheet is dismissed (whereas he implemented presentationControllerDidAttemptToDismiss, which is called when the user tries and fails to dismiss the sheet), and (b) setting isModalInPresentation is entirely orthogonal and in fact will make the presented sheet undismissable (which is the opposite of what OP wants).

Solution 3 - Ios

Another option to get back viewWillAppear and viewDidAppear is set

let vc = UIViewController()
vc.modalPresentationStyle = .fullScreen

this option cover full screen and after dismiss, calls above methods

Solution 4 - Ios

For future readers here is a more complete answer with implementation:

  1. In the root view controllers prepare for segue add the following (Assuming your modal has a nav controller)
    // Modal Dismiss iOS 13
    modalNavController.presentationController?.delegate = modalVc
  1. In the modal view controller add the following delegate + method
// MARK: - iOS 13 Modal (Swipe to Dismiss)

extension ModalViewController: UIAdaptivePresentationControllerDelegate {
    func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
        

        print("slide to dismiss stopped")
        self.dismiss(animated: true, completion: nil)
    }
}
  1. Ensure in the modal View Controller that the following property is true in order for the delegate method to be called
    self.isModalInPresentation = true
  1. Profit

Solution 5 - Ios

Swift

General Solution to call viewWillAppear in iOS13

class ViewController: UIViewController {
        
        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            print("viewWillAppear")
        }
        
        //Show new viewController
        @IBAction func show(_ sender: Any) {
            let newViewController = NewViewController()
            //set delegate of UIAdaptivePresentationControllerDelegate to self
            newViewController.presentationController?.delegate = self
            present(newViewController, animated: true, completion: nil)
        }
    }
    
    extension UIViewController: UIAdaptivePresentationControllerDelegate {
        public func presentationControllerDidDismiss( _ presentationController: UIPresentationController) {
            if #available(iOS 13, *) {
                //Call viewWillAppear only in iOS 13
                viewWillAppear(true)
            }
        }
    }

Solution 6 - Ios

Override viewWillDisappear on the UIViewController that's being dismissed. It will alert you to a dismissal via isBeingDismissed boolean flag.

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    if isBeingDismissed {
        print("user is dismissing the vc")
    }
}

** If the user is halfway through the swipe down and swipes the card back up, it'll still register as being dismissed, even if the card is not dismissed. But that's an edge case you may not care about.

Solution 7 - Ios

If you want to do something when user closes the modal sheet from within that sheet. Let's assume you already have some Close button with an @IBAction and a logic to show an alert before closing or do something else. You just want to detect the moment when user makes push down on such a controller.

Here's how:

class MyModalSheetViewController: UIViewController {

     override func viewDidLoad() {
        super.viewDidLoad()

        self.presentationController?.delegate = self
     }

     @IBAction func closeAction(_ sender: Any) {
         // your logic to decide to close or not, when to close, etc.
     }

}

extension MyModalSheetViewController: UIAdaptivePresentationControllerDelegate {

    func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
        return false // <-prevents the modal sheet from being closed
    }

    func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
        closeAction(self) // <- called after the modal sheet was prevented from being closed and leads to your own logic
    }
}

Solution 8 - Ios

DRAG OR CALL DISMISS FUNC will work with below code.

  1. In root view controller, you tell that which is its presentation view controller as below code

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "presenterID" { let navigationController = segue.destination as! UINavigationController if #available(iOS 13.0, *) { let controller = navigationController.topViewController as! presentationviewcontroller // Modal Dismiss iOS 13 controller.presentationController?.delegate = self } else { // Fallback on earlier versions } navigationController.presentationController?.delegate = self

     }
    

    }

  2. Again in the root view controller, you tell what you will do when its presentation view controller is dissmised

    public func presentationControllerDidDismiss( _ presentationController: UIPresentationController) { print("presentationControllerDidDismiss") }

  3. In the presentation view controller, When you hit cancel or save button in this picture. Below code will be called.The

    self.dismiss(animated: true) { self.presentationController?.delegate?.presentationControllerDidDismiss?(self.presentationController!) }

enter image description here

Solution 9 - Ios

in SwiftUI you can use onDismiss closure

func sheet<Item, Content>(item: Binding<Item?>, onDismiss: (() -> Void)?, content: (Item) -> Content) -> some View

Solution 10 - Ios

If someone doesn't have access to the presented view controller, they can just override the following method in presenting view controller and change the modalPresentationStyle to fullScreen or can add one of the strategies mentioned above with this approach

 override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
    if let _ = viewControllerToPresent as? TargetVC {
        viewControllerToPresent.modalPresentationStyle = .fullScreen
    }
    super.present(viewControllerToPresent, animated: flag, completion: completion)
}

if presented view controller is navigation controller and you want to check the root controller, can change the above condition to be like

if let _ = (viewControllerToPresent as? UINavigationController)?.viewControllers.first as? TargetVC {
   viewControllerToPresent.modalPresentationStyle = .fullScreen
}

Solution 11 - Ios

If you used the ModalPresentationStyle in FullScreen, the behavior of the controller is back as usual.

ConsultarController controllerConsultar = this.Storyboard.InstantiateViewController("ConsultarController") as ConsultarController; controllerConsultar.ModalPresentationStyle = UIModalPresentationStyle.FullScreen; this.NavigationController.PushViewController(controllerConsultar, true);

Solution 12 - Ios

From my point of view, Apple should not set pageSheet is the default modalPresentationStyle

I'd like to bring fullScreen style back to default by using swizzling

Like this:

private func _swizzling(forClass: AnyClass, originalSelector: Selector, swizzledSelector: Selector) {
    if let originalMethod = class_getInstanceMethod(forClass, originalSelector),
       let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector) {
        method_exchangeImplementations(originalMethod, swizzledMethod)
    }
}

extension UIViewController {

    static func preventPageSheetPresentationStyle () {
        UIViewController.preventPageSheetPresentation
    }
    
    static let preventPageSheetPresentation: Void = {
        if #available(iOS 13, *) {
            _swizzling(forClass: UIViewController.self,
                       originalSelector: #selector(present(_: animated: completion:)),
                       swizzledSelector: #selector(_swizzledPresent(_: animated: completion:)))
        }
    }()

    @available(iOS 13.0, *)
    private func _swizzledPresent(_ viewControllerToPresent: UIViewController,
                                        animated flag: Bool,
                                        completion: (() -> Void)? = nil) {
        if viewControllerToPresent.modalPresentationStyle == .pageSheet
                   || viewControllerToPresent.modalPresentationStyle == .automatic {
            viewControllerToPresent.modalPresentationStyle = .fullScreen
        }
        _swizzledPresent(viewControllerToPresent, animated: flag, completion: completion)
    }
}

And then put this line to your AppDelegate

UIViewController.preventPageSheetPresentationStyle()

Solution 13 - Ios

wouldn't it be simple to call the presentingViewController.viewWillAppear? befor dismissing?

self.presentingViewController?.viewWillAppear(false)
self.dismiss(animated: true, completion: nil)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMarcos TanakaView Question on Stackoverflow
Solution 1 - IosmattView Answer on Stackoverflow
Solution 2 - IosSuddenMoustacheView Answer on Stackoverflow
Solution 3 - IosPiterPanView Answer on Stackoverflow
Solution 4 - IosJerland2View Answer on Stackoverflow
Solution 5 - IosdimohamdyView Answer on Stackoverflow
Solution 6 - IoscraftView Answer on Stackoverflow
Solution 7 - IosVitaliiView Answer on Stackoverflow
Solution 8 - IoscodersView Answer on Stackoverflow
Solution 9 - IosAlirezakView Answer on Stackoverflow
Solution 10 - IosKamran KhanView Answer on Stackoverflow
Solution 11 - IosAbelardo del angel QuirozView Answer on Stackoverflow
Solution 12 - IosjacobView Answer on Stackoverflow
Solution 13 - IosMikesch8764View Answer on Stackoverflow