At WWDC 2019, Apple announced a new "card-style" look for modal presentations, which brought along with it built-in gestures for dismissing modal view controllers by swiping down on the card. They also introduced the new isModalInPresentation
property on UIViewController
so that you can disallow this dismissal behavior if you so choose.
So far, though, I have found no way to emulate this behavior in SwiftUI. Using the .presentation(_ modal: Modal?)
, does not, as far as I can tell, allow you to disable the dismissal gestures in the same way. I also attempted putting the modal view controller inside a UIViewControllerRepresentable
View
, but that didn't seem to help either:
struct MyViewControllerView: UIViewControllerRepresentable { func makeUIViewController(context: UIViewControllerRepresentableContext<MyViewControllerView>) -> UIHostingController<MyView> { return UIHostingController(rootView: MyView()) } func updateUIViewController(_ uiViewController: UIHostingController<MyView>, context: UIViewControllerRepresentableContext<MyViewControllerView>) { uiViewController.isModalInPresentation = true } }
Even after presenting with .presentation(Modal(MyViewControllerView()))
I was able to swipe down to dismiss the view. Is there currently any way to do this with existing SwiftUI constructs?
As per pawello2222's answer below, this is now supported by the new interactiveDismissDisabled(_:)
API.
struct ContentView: View { @State private var showSheet = false var body: some View { Text("Content View") .sheet(isPresented: $showSheet) { Text("Sheet View") .interactiveDismissDisabled(true) } } }
I wanted to do this as well, but couldn't find the solution anywhere. The answer that hijacks the drag gesture kinda works, but not when it's dismissed by scrolling a scroll view or form. The approach in the question is less hacky also, so I investigated it further.
For my use case I have a form in a sheet which ideally could be dismissed when there's no content, but has to be confirmed through a alert when there is content.
My solution for this problem:
struct ModalSheetTest: View { @State private var showModally = false @State private var showSheet = false var body: some View { Form { Toggle(isOn: self.$showModally) { Text("Modal") } Button(action: { self.showSheet = true}) { Text("Show sheet") } } .sheet(isPresented: $showSheet) { Form { Button(action: { self.showSheet = false }) { Text("Hide me") } } .presentation(isModal: self.showModally) { print("Attempted to dismiss") } } } }
The state value showModally
determines if it has to be showed modally. If so, dragging it down to dismiss will only trigger the closure which just prints "Attempted to dismiss" in the example, but can be used to show the alert to confirm dismissal.
struct ModalView<T: View>: UIViewControllerRepresentable { let view: T let isModal: Bool let onDismissalAttempt: (()->())? func makeUIViewController(context: Context) -> UIHostingController<T> { UIHostingController(rootView: view) } func updateUIViewController(_ uiViewController: UIHostingController<T>, context: Context) { context.coordinator.modalView = self uiViewController.rootView = view uiViewController.parent?.presentationController?.delegate = context.coordinator } func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, UIAdaptivePresentationControllerDelegate { let modalView: ModalView init(_ modalView: ModalView) { self.modalView = modalView } func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool { !modalView.isModal } func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) { modalView.onDismissalAttempt?() } } } extension View { func presentation(isModal: Bool, onDismissalAttempt: (()->())? = nil) -> some View { ModalView(view: self, isModal: isModal, onDismissalAttempt: onDismissalAttempt) } }
This is perfect for my use case, hope it helps you or someone else out as well.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With