Captura cuando el usuario arrastra hacia abajo modal en iOS Swift

Estoy tratando de obtener el mismo comportamiento que Apple ha hecho en su aplicación de calendario para iOS15 (posiblemente también en versiones anteriores) donde si intenta descartar la vista, se le presentará una hoja de acción que le preguntará si desea descartar sus cambios.

Entonces, lo que quiero es poder capturar de alguna manera el evento donde el usuario intenta arrastrar hacia abajo la vista y, por ejemplo, verificar:

if hasChanges {
// Show Action sheet & stop view from disappearing 
}

Esto no funciona como no quiero en viewWillDisappear ya que la vista simplemente desaparece antes de que pueda presentar la hoja de acción

ingrese la descripción de la imagen aquí

Answer

Como se describe en los documentos de Apple , puede implementar UIAdaptivePresentationControllerDelegatey usar presentationControllerDidAttemptToDismiss(_ :)para "interceptar" la acción desplegable.

Aquí hay un ejemplo básico:

class ConfirmDismissViewController: UIViewController, UIAdaptivePresentationControllerDelegate {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .systemYellow
        
        // add a dismiss button
        let b = UIButton(type: .system)
        b.setTitle("Dismiss", for: [])
        b.addTarget(self, action: #selector(btnTapped(_:)), for: .touchUpInside)
        b.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(b)
        NSLayoutConstraint.activate([
            b.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            b.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        ])
        
        // this will trigger a call to presentationControllerDidAttemptToDismiss() on drag-down
        isModalInPresentation = true

        presentationController?.delegate = self
    }

    func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
        
        let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
        
        // Only ask if the user wants to save if they attempt to pull to dismiss, not if they tap Cancel.
        alert.addAction(UIAlertAction(title: "Discard Changes", style: .destructive) { _ in
            self.dismiss(animated: true, completion: nil)
        })
        
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
        
        present(alert, animated: true, completion: nil)

    }

    @objc func btnTapped(_ sender: Any?) -> Void {
        // dismiss WITHOUT prompt
        dismiss(animated: true, completion: nil)
    }
    
}

Cuando presenta este controlador de vista, el usuario puede tocar el botón "Descartar" para descartar explícitamente el VC o, si el usuario arrastra hacia abajo, se le preguntará.