Reputation: 1031
I am working on a MacOS app that can process images. The idea is the app hides out of the way most of the time, but if you drag an image from another app or the finder, my app will show and you can drag the image on top of it. Basically a drop zone on standby for when you need it.
Everything is working perfectly except I can't figure out how to only show the app for certain types of draggable items (URLs, fileURLs, FilePromises and Images). What I have now shows the app for any kind of drag, even selecting text on a page or clicking and clicking and dragging through the menu bar.
I've tried looking at the NSPasteboard for dragging, but that doesn't seem to be updated at drag time. I've seen some posts about using accessibility to see what's under the mouse, but that seems brittle and I'm not yet understanding how to do it.
Here is the code I'm using to detect global drag and drop:
dragMonitor = NSEvent.addGlobalMonitorForEvents(matching:.leftMouseDragged) { event in
if !self.isDragging {
self.isDragging = true
if let dropzoneViewController = self.dropzoneViewController, dropzoneViewController.shouldShowForDrag(event: event) {
self.show()
}
}
}
upMonitor = NSEvent.addGlobalMonitorForEvents(matching:.leftMouseUp) { event in
if self.isDragging {
self.hide()
self.isDragging = false
}
}
That function, in turn, calls the following, which applies the app's logic for determining whether to handle a drag or not.
func shouldShowForDrag(event: NSEvent) -> Bool {
return self.dropTarget.canHandleDrop(NSPasteboard(name: .drag))
}
For clarity's sake, here's how the app handles drags once they are over the app's window:
override func performDragOperation(_ draggingInfo: NSDraggingInfo) -> Bool {
isReceivingDrag = false
if let dropTarget = dropTarget, dropTarget.canHandleDrop(draggingInfo.draggingPasteboard) {
dropTarget.handleDrop(draggingInfo.draggingPasteboard)
return true
} else {
return false
}
}
The only difference between those two checks is the global check (shouldShowForDrag(event:)
) uses NSPasteboard(name: .drag)
which is not current at the time NSEvent.addGlobalMonitorForEvents(matching:)
fires. The logic when the draggable enters my window uses the provided pasteboard (draggingInfo.draggingPasteboard
) which, of course, is accurate to what's being dragged.
Finally, here's the basic logic for determining what drags to accept:
func canHandleDrop(_ pasteBoard: NSPasteboard) -> Bool {
let urlFilteringOptions = [NSPasteboard.ReadingOptionKey.urlReadingContentsConformToTypes:NSImage.imageTypes]
if let urls = pasteBoard.readObjects(forClasses: [NSURL.self], options:urlFilteringOptions) as? [URL], urls.count > 0 {
return true
} else if let filePromises = pasteBoard.readObjects(forClasses: [NSFilePromiseReceiver.self], options: nil) as? [NSFilePromiseReceiver], filePromises.count > 0 {
return true
} else if let images = pasteBoard.readObjects(forClasses: [NSImage.self], options: [:]) as? [NSImage], images.count > 0 {
return true
}
return false
}
The first two clauses are the most important. Detecting NSImages is not strictly required.
I know it can be done because I'm using other apps (to do similar, but different, things), and they work exactly like I'm trying to achieve. But so far I'm banging my head against the wall.
Thanks
Upvotes: 1
Views: 635
Reputation: 21
You can simply check the drag pasteboard for the content types
let pasteboard = NSPasteboard(name: .drag)
if let types = pasteboard.types, types.contain(.fileURL) {
if !self.isDragging {
self.isDragging = true
if let dropzoneViewController = self.dropzoneViewController, dropzoneViewController.shouldShowForDrag(event: event) {
self.show()
}
}
}
Upvotes: 0