Skip to content

Instantly share code, notes, and snippets.

@mobilinked
Last active February 9, 2024 13:03
Show Gist options
  • Star 36 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save mobilinked/9b6086b3760bcf1e5432932dad0813c0 to your computer and use it in GitHub Desktop.
Save mobilinked/9b6086b3760bcf1e5432932dad0813c0 to your computer and use it in GitHub Desktop.
SwiftUI - prevent auto dismiss the sheet by drag down
//
// Created by https://quickplan.app on 2020/11/8.
//
import SwiftUI
/// Control if allow to dismiss the sheet by the user actions
/// - Drag down on the sheet on iPhone and iPad
/// - Tap outside the sheet on iPad
/// No impact to dismiss programatically (by calling "presentationMode.wrappedValue.dismiss()")
/// -----------------
/// Tested on iOS 14.2 with Xcode 12.2 RC
/// This solution may NOT work in the furture.
/// -----------------
struct MbModalHackView: UIViewControllerRepresentable {
var dismissable: () -> Bool = { false }
func makeUIViewController(context: UIViewControllerRepresentableContext<MbModalHackView>) -> UIViewController {
MbModalViewController(dismissable: self.dismissable)
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
}
}
extension MbModalHackView {
private final class MbModalViewController: UIViewController, UIAdaptivePresentationControllerDelegate {
let dismissable: () -> Bool
init(dismissable: @escaping () -> Bool) {
self.dismissable = dismissable
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
setup()
}
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
dismissable()
}
// set delegate to the presentation of the root parent
private func setup() {
guard let rootPresentationViewController = self.rootParent.presentationController, rootPresentationViewController.delegate == nil else { return }
rootPresentationViewController.delegate = self
}
}
}
extension UIViewController {
fileprivate var rootParent: UIViewController {
if let parent = self.parent {
return parent.rootParent
}
else {
return self
}
}
}
/// make the call the SwiftUI style:
/// view.allowAutDismiss(...)
extension View {
/// Control if allow to dismiss the sheet by the user actions
public func allowAutoDismiss(_ dismissable: @escaping () -> Bool) -> some View {
self
.background(MbModalHackView(dismissable: dismissable))
}
/// Control if allow to dismiss the sheet by the user actions
public func allowAutoDismiss(_ dismissable: Bool) -> some View {
self
.background(MbModalHackView(dismissable: { dismissable }))
}
}
/// Example:
struct ContentView: View {
@State private var presenting = false
var body: some View {
VStack {
Button {
presenting = true
} label: {
Text("Present")
}
}
.sheet(isPresented: $presenting) {
ModalContent()
.allowAutoDismiss { false }
// or
// .allowAutoDismiss(false)
}
}
}
struct ModalContent: View {
@Environment(\.presentationMode) private var presentationMode
var body: some View {
VStack {
Text("Hello")
.padding()
Button {
presentationMode.wrappedValue.dismiss()
} label: {
Text("Dismiss")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
@USBA
Copy link

USBA commented Nov 9, 2020

Thanks @mobilinked, you're a lifesaver.

@YanSte
Copy link

YanSte commented Nov 20, 2020

Thanks !

@slavabily
Copy link

Brilliant solution! Thanks so much!

@AntonBrock
Copy link

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment