Skip to content

Instantly share code, notes, and snippets.

@shnhrrsn
Created March 9, 2026 05:35
Show Gist options
  • Select an option

  • Save shnhrrsn/439d05ecf8344405bebf56f1a3108b31 to your computer and use it in GitHub Desktop.

Select an option

Save shnhrrsn/439d05ecf8344405bebf56f1a3108b31 to your computer and use it in GitHub Desktop.
import SwiftUI
// MARK: - DismissInterceptor
/// Overrides `@Environment(\.dismiss)` for child views by injecting a custom
/// `Binding<PresentationMode>` via the deprecated `\.presentationMode` key path.
///
/// `DismissAction` internally wraps `Binding<PresentationMode>` and calls its setter
/// when `dismiss()` is invoked. By replacing that binding, we redirect dismiss to our
/// handler instead of letting it close the window (macOS) or presentation (iOS).
///
/// This lets views use `@Environment(\.dismiss)` normally without knowing they're
/// inside a subscreen overlay.
struct DismissInterceptor: ViewModifier {
let onDismiss: @MainActor () -> Void
/// presentationMode is get-only at the API level, but EnvironmentKey's subscript
/// requires a WritableKeyPath — so the runtime cast succeeds.
/// If Apple changes the internal layout, the cast returns nil and we fall through
/// to a no-op (dismiss closes the window as before).
private static let presentationModeKeyPath: WritableKeyPath<EnvironmentValues, Binding<PresentationMode>>? = {
guard let keyPath = \EnvironmentValues.presentationMode as? WritableKeyPath else {
assertionFailure()
return nil
}
return keyPath
}()
func body(content: Content) -> some View {
if let keyPath = Self.presentationModeKeyPath {
let presented = true
let pm = withUnsafeBytes(of: presented) { $0.load(as: PresentationMode.self) }
let binding = Binding<PresentationMode>(
get: { pm },
set: { _ in onDismiss() },
)
content.environment(keyPath, binding)
} else {
content
}
}
}
// MARK: - View + interceptDismiss
extension View {
/// Intercepts `@Environment(\.dismiss)` calls from child views,
/// redirecting them to the provided handler.
func interceptDismiss(_ handler: @MainActor @escaping () -> Void) -> some View {
modifier(DismissInterceptor(onDismiss: handler))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment