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()
}
}
@pryder-fleetaero
Copy link

pryder-fleetaero commented Aug 12, 2022

Did anything in iOS 16 get announced which would implement this functionality natively in SwiftUI?

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