Skip to content

Instantly share code, notes, and snippets.

@peterfriese
Last active April 10, 2025 03:14
Show Gist options
  • Save peterfriese/8fb3d76bdbe21b84495b79b3a86bf898 to your computer and use it in GitHub Desktop.
Save peterfriese/8fb3d76bdbe21b84495b79b3a86bf898 to your computer and use it in GitHub Desktop.
This is an enhanced version of Apple's `interactiveDismissDisabled` view modifier which allows you to act on the user's attempt to dismiss a sheet. See my article for more details. I filed a feedback for a feature request to add this to SwiftUI: FB9782213 (https://openradar.appspot.com/FB9782213)
import SwiftUI
extension View {
public func interactiveDismissDisabled(_ isDisabled: Bool = true, onAttemptToDismiss: (() -> Void)? = nil) -> some View {
InteractiveDismissableView(view: self, isDisabled: isDisabled, onAttemptToDismiss: onAttemptToDismiss)
}
public func interactiveDismissDisabled(_ isDisabled: Bool = true, attemptToDismiss: Binding<Bool>) -> some View {
InteractiveDismissableView(view: self, isDisabled: isDisabled) {
attemptToDismiss.wrappedValue.toggle()
}
}
}
private struct InteractiveDismissableView<T: View>: UIViewControllerRepresentable {
let view: T
let isDisabled: Bool
let onAttemptToDismiss: (() -> Void)?
func makeUIViewController(context: Context) -> UIHostingController<T> {
UIHostingController(rootView: view)
}
func updateUIViewController(_ uiViewController: UIHostingController<T>, context: Context) {
context.coordinator.dismissableView = self
uiViewController.rootView = view
uiViewController.parent?.presentationController?.delegate = context.coordinator
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UIAdaptivePresentationControllerDelegate {
var dismissableView: InteractiveDismissableView
init(_ dismissableView: InteractiveDismissableView) {
self.dismissableView = dismissableView
}
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
!dismissableView.isDisabled
}
func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
dismissableView.onAttemptToDismiss?()
}
}
}
struct ContentView: View {
@State var showingSheet = false
@State var name: String = "Johnny Appleseed"
var body: some View {
Form {
Section("User Profile") {
Text(name)
}
Button("Edit", action: { showingSheet.toggle() })
}
.sheet(isPresented: $showingSheet) {
EditView(name: $name)
}
}
}
private class ViewModel: ObservableObject {
@Published var name: String
private var original: String
var isModified: Bool {
print("\(name) - \(original)")
return name != original
}
init(name: String) {
self.name = name
self.original = name
}
}
private struct EditView: View {
@Environment(\.dismiss) var dismiss
@Binding var name: String
@StateObject private var viewModel: ViewModel
@State var showingConfirmationDialog = false
init(name: Binding<String>) {
self._name = name
self._viewModel = StateObject(wrappedValue: ViewModel(name: name.wrappedValue))
}
var body: some View {
NavigationView {
Form {
TextField("Enter your name", text: $viewModel.name)
}
.navigationTitle("Edit")
.navigationBarTitleDisplayMode(.inline)
}
.interactiveDismissDisabled(viewModel.isModified) {
showingConfirmationDialog.toggle()
}
.confirmationDialog("", isPresented: $showingConfirmationDialog) {
Button("Save") {
name = viewModel.name
dismiss()
}
Button("Discard", role: .destructive) {
dismiss()
}
Button("Cancel", role: .cancel) { }
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
@charliecm
Copy link

Hey man, how do i get rid of the white spaces in the safe areas?

@isaiah-a97 I encountered the same issue. Turns out I just needed to add .ignoresSafeArea(.all) to the view after the interactiveDismissDisabled modifier to remove the safe area.

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