I was also struggling with this issue - draggable views within a popover could not be dropped outside of it. The popover must be dismissed before drop destinations in the presenting view can be detected.
As the OP pointed out, it is possible to detect when a drag gesture starts, but a better workaround I found is using DropDelegate.
The basic idea is to attach a custom delegate to the popover that detects when the dragged view exits its bounds, allowing us to control when we want to dismiss it.
/// A work-around thst allows `.draggable` views presented within a `.popover` modal
/// to be dropped outside of the popover.
/// ```swift
/// NavigationStack {
/// Text("Drop Zone")
/// .dropDestination(for: String.self) { items, location in
/// true
/// } isTargeted: {
/// print("isTargeted \($0)")
/// }
/// .toolbar {
/// ToolbarItem(placement: .topBarTrailing) {
/// Button(“Press Me”) {
/// isPresented = true
/// }
/// .popover(isPresented: $isPresented) {
/// NavigationStack {
/// ZStack {
/// Color.green.ignoresSafeArea()
/// Color.red.frame(width: 50, height: 50).draggable("Payload")
/// }
/// .navigationTitle("Modal")
/// }
/// .frame(minWidth: 300, minHeight: 500)
/// .onDrop(of: [.plainText], delegate: ExitDropDelegate(onDropExit: { isPresented = false }))
/// }
/// }
/// }
/// }
/// ```
/// In this example, the drop zone in the outer `NavigationStack` does not detect the draggable
/// view hovering over it, due to the popover being modally presented. The `ExitDropDelegate`
/// dismisses the popover when the view is dragged outside of its bounds, allowing the drop zone to
/// be targeted.
struct ExitDropDelegate: DropDelegate {
var onDropExit: (() -> Void)? = nil
func performDrop(info: DropInfo) -> Bool { false }
func dropUpdated(info: DropInfo) -> DropProposal? {
/// Hides the green plus button
.init(operation: .move)
}
func dropExited(info: DropInfo) { onDropExit?() }
}
This approach can be extended with more complex logic, for example if we want to dismiss the popover only after the user hovers the view outside of the popover for a certain period of time, then we can add a timer to the DropDelegate
that starts when the dropExited
is called, and invalidated when dropEntered
is called before the timer fires.