SwiftUI view detect modal dismissal

173 Views Asked by At

I have a SwiftUI view wrapped in UIHostingController and presented modally as formSheet on iPad. I have a done button to dismiss and have a call back closure passed from parent view controller to perform actions when this done button is pressed. The problem happens on iPad where the user may tap outside of the view and the view gets dismissed thereby missing the callback.

One way to solve it would be to trigger the callback closure in onDismiss of the SwiftUI view. While it works, I am not sure if it is the right place and if it is reliable enough. The documentation only says that onDismiss is called when the "view disappears from the screen". However, what if we push a view on top of this view in a navigation hierarchy, will onDismiss be still called?

1

There are 1 best solutions below

0
Sweeper On BEST ANSWER

From your description, I assume you have a SwiftUI view like this:

struct SomeView: View {
    let callback: () -> Void
    
    @Environment(\.dismiss) var dismiss
    
    var body: some View {
        NavigationStack {
            Text("Foo")
                .toolbar {
                    ToolbarItem(placement: .topBarTrailing) {
                        Button("Done") {
                            callback()
                            dismiss()
                        }
                    }
                }
        }
    }
}

When presenting the hosting controller, you can set its presentationController's delegate and implement the delegate method presentationControllerDidDismiss. For example:

@objc func somethingIsClicked() {
    let host = UIHostingController(rootView: SomeView(callback: callback))
    host.modalPresentationStyle = .formSheet
    host.presentationController?.delegate = self
    present(host, animated: true)
}

func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
    callback()
}

func callback() {
    print("Dismissed!")
}

presentationControllerDidDismiss will be called when the sheet is completely dismissed, not when the user "starts" to dismiss it (which can be detected with presentationControllerWillDismiss), because at this point the user can change their mind and stop dismissing.

As its documentation says, presentationControllerDidDismiss isn't called when the sheet is dismissed programmatically, so you still need to pass the callback to the SwiftUI view.


Also note that you can set:

host.isModalInPresentation = true

to force the user to dismiss the sheet using your done button.