You might consider using addLocalMonitorForEvents
(I thought about addGlobalMonitorForEvents
, but... as illustrated here, it would require for the app to have accessibility access)
However, as noted by the OP in the comments:
it's hide app only after release of mouse button. For some reason collectionView holds drawning of the window (in my case it is NSPanel). And hideApp() is called ONLY AFTER I drop mouse button (I see this in logs)
So instead, let's try another to monitor the dragging session status.
Reading "Supporting Table View Drag and Drop Through File Promises", I see:
When a drag starts, you adopt the NSPasteboardWriting
protocol to write data to the NSPasteboard
. When a drag occurs, you determine the valid drop target. When the drag ends, you read the drag data from the NSPasteboard
."
Picking up on that:
import AppKit
import SwiftUI
public class NSCollectionController<Content: View>: NSViewController, NSCollectionViewDelegate, NSCollectionViewDataSource, QLPreviewPanelDataSource, QLPreviewPanelDelegate {
// Flag to check whether the app is currently visible.
static var appShown = true
// A helper object for automatically scrolling the collection view.
var automaticScroller: AutomaticScroller!
// NSCollectionViewDelegate
// This function is called when the user starts dragging an item.
// We return our custom pasteboard writer, which also conforms to NSDraggingSource, for the dragged item.
public func collectionView(_ collectionView: NSCollectionView, pasteboardWriterForItemAt indexPath: IndexPath) -> NSPasteboardWriting? {
return MyPasteboardWriter()
}
// This function is called when a dragging session ends. At this point, we reset our appShown flag to true.
public func collectionView(_ collectionView: NSCollectionView, draggingSession session: NSDraggingSession, endedAt screenPoint: NSPoint, dragOperation operation: NSDragOperation) {
NSCollectionController.appShown = true
}
// A helper function to hide the app.
static func hideApp() {
DispatchQueue.main.async {
NSApplication.shared.hide(nil)
}
appShown = false
// Here you would call a function to update the automatic scroller.
// automaticScroller.updStatus(appDisplayed: appShown)
}
// Our custom pasteboard writer. This class also implements NSDraggingSource to handle the dragging of the item.
private class MyPasteboardWriter: NSObject, NSPasteboardWriting, NSDraggingSource {
// NSPasteboardWriting
// This function returns the types of data that this object can write to the pasteboard.
func writableTypes(for pasteboard: NSPasteboard) -> [NSPasteboard.PasteboardType] {
// You need to implement this method based on the data your items can represent.
// For example, if your items can be represented as strings, you can return [.string].
}
// This function returns a property list that represents the data of this object for a specific type.
func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? {
// You need to implement this method based on the data of your item for the given type.
// For example, if your items can be represented as strings and type is .string, you can return the string representation of your item.
}
// NSDraggingSource
// This function returns the allowed operations (like .copy, .move) when the dragging is outside the source application.
func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation {
return [.copy, .move]
}
// This function is called when the dragging image is moved.
// Here we check if the mouse is outside the app window, and if so, we hide the app.
func draggingSession(_ session: NSDraggingSession, movedTo screenPoint: NSPoint) {
guard let window = NSApplication.shared.mainWindow, NSCollectionController.appShown else { return }
let windowRectInScreenCoordinates = window.convertToScreen(window.frame)
if !windowRectInScreenCoordinates.contains(screenPoint) {
NSCollectionController.hideApp()
}
}
// This function is called when the drag operation ends. There is no need to do anything here in this case.
func draggingSession(_ session: NSDraggingSession, endedAt
func draggingSession(_ session: NSDraggingSession, endedAt screenPoint: NSPoint, operation: NSDragOperation) {
// You can add any cleanup operations here after a drag operation ends
}
}
}
The NSCollectionController
class is a controller for an NSCollectionView
. It handles many tasks, including acting as the delegate and data source for the collection view, and managing the drag-and-drop interactions.
In order to hide the entire application when a dragged item is moved outside the application window, the idea is to a custom class (MyPasteboardWriter
) that conforms to both NSPasteboardWriting
and NSDraggingSource
protocols.
The NSPasteboardWriting
protocol enables the class to provide data to the pasteboard (which is used during drag-and-drop operations), while NSDraggingSource
allows it to react to drag-and-drop events.
In the NSDraggingSource
protocol, the draggingSession(_:movedTo:)
method is implemented to check the location of the dragged item. If the item is moved outside the application window, the application is hidden. This is done by using the NSApplication.shared.hide(nil)
function.
The appShown
static variable is used to keep track of whether the application is currently visible or not. It's important to prevent the application from attempting to hide multiple times in succession.
The draggingSession(_:sourceOperationMaskFor:)
method is also implemented to specify the allowed operations (.copy, .move) when the dragging is outside the source application.
Finally, the collectionView(_:draggingSession:endedAt:dragOperation:)
delegate method is used to reset the appShown
flag back to true when a dragging session ends, indicating that the application can now be shown again.
movedTo
function never called, so app cannot be hidden.
Make sure you properly set up the dragging session and the item you are dragging uses your custom MyPasteboardWriter
as its pasteboard writer.
The class that adopts the NSDraggingSource
protocol and implements the draggingSession(_:movedTo:)
method must be the one used as the source object when initiating the dragging session.
If you are using a different object as the source, the method won't be called.