Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment