Last active
February 9, 2024 13:03
-
-
Save mobilinked/9b6086b3760bcf1e5432932dad0813c0 to your computer and use it in GitHub Desktop.
SwiftUI - prevent auto dismiss the sheet by drag down
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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() | |
} | |
} |
Thanks !
Brilliant solution! Thanks so much!
Thank you!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks @mobilinked, you're a lifesaver.