First of all a good and comprehensive tutorial (as usual) which gives a good head start for a future project.
One thing that hampered me during the tutorial is the function shaphot() instead of snapshot() (used in the mouseDown functions)
I like to type with the tutorials instead of using copy/paste. Therefore i had to reread the sentence multiple times before i discovered why i got an error. I assume it is a typo, if this is not the case a explanation or comment would be nice
Why are two different kinds needed to check whether the data can be dragged and dropped? We already define the types we accept in register(forDraggedTypes: Array(acceptableTypes))
why do we actually have to check the pasteboard a 2nd time in shouldAllowDrag(_:)
Its a matter of refining what you want from the dragging session. So you define one of the acceptable types as URL so any URL but then in shouldAllowDrag(_:) you filter out to only URLs that are images using the criteria NSImage.imageTypes() . Think of it as a bag of M&M’s and you only want the yellow ones :]
Do you have any pointers on displaying a modal window in the performDragOperation() method? What I found is that I cannot enter text in an NSTextField of a modal window because the drag operation is not complete. After some time, it seems to time out, at which point I can enter text in the modal window. Clicking works, as things like NSAlert() run modally allow clicking the buttons.
I’ve tried a variety of dispatch_*() methods, none of which helped.
Warren, I really appreciate you taking the time to answer! I was surprised to hear this worked for you as the first thing I tried was dispatch_async to the main thread. I added this new code to the completed project and found two things:
With what you implemented, you actually don’t even need to async dispatch to main
The big difference between what you implemented and the problem I’m having is that performAction() is only triggered from a drag source inside the application.
My test case is dragging a file URL from Finder (or elsewhere) into my app. Fortunately, your tutorial has the perfect place to evaluate this case. I moved the DispatchQueue.main.async snippet into processImageURLs(), which handles JPEGs dropped from Finder, and was able to confirm that dispatch async no longer solves the problem…
It actually worked once out of several runs. I believe using dispatch_async is creating a race condition. If the drag happens to finish first, the text fields are responsive. If the drag does not finish before the new window is made key then text fields cannot respond.
Im getting a similar result as you when i attempt to pop up the modal window at the end of processImageURLs . The window appears and can receive events but the Finder spins-locks and eventually the drag times out and the drag images slide back.
Its a very naive-implementation in the tutorial which loads the images from URL in the main thread (you can spin lock the app by dumping a large number of images in at once) so im wondering if you load the images in a background thread then do a final render in the main thread it might sort it out.
Yes - If you throw the image generation out into the background it works itself out. Passes the test cases of the modal receiving events and the Finder not spin locking. I threw a dozen large images at it and it seems to work.
func processImageURLs(_ urls: [URL], center: NSPoint) {
DispatchQueue.global(qos: .background).async {
var images:[NSImage] = []
for url in urls {
//1.
if let image = NSImage(contentsOf:url) {
images.append(image)
}
}
DispatchQueue.main.async {
for (index,image) in images.enumerated() {
var newCenter = center
//2.
if index > 0 {
newCenter = center.addRandomNoise(Appearance.randomNoise)
}
//3.
self.processImage(image, center:newCenter)
}
self.showAModalWindow()
}
}
}
As you mention I wanted to demonstrate a delegation/decoupling pattern by having a utility view that just handled the dragging while handing off its relevant events to another object.
The other reason was that I was initially going to do some painting (drawing of the sticker outline) in that view which went over the top of everything else but the tutorial got too long and I had to cut the feature.
Also you are still going to need to make a custom NSView to handle the NSDraggingDestination events. You dont register view controllers for dragging events. Only NSView and NSWindow can do that.
To read the URLs of dragging files, without dragging them into the view controller, should be possible. But therefore the DestinationView should not be declared as a NSView-class. Is that right?
Which class I have to use, to read the dragged file URL in the same time, my left mouse clicks on the file, which is located outside my app?
Hi Tom - I do like Objective-C too. I spend my working day in a large mixed codebase of OC and Swift and sometimes Swift just gets a bit unreadable especially when a developer gets a bit too clever.
The Razeware site is exclusively Swift for Apple development so you are unlikely to see a OC version of this tutorial. Try rewriting the code in the tutorial as objective-c. Its not much different for a simple API based project like this and its good practise for working with legacy code.
No, it shouldn’t be possible. Dragging is a secure user initiated action.
If the destination view wasn’t a NSView it couldn’t be part of the view hierarchy.
As for knowing what file has received a right click in Finder. Why do you think Finder would supply this information to the OS. That wouldn’t be very secure.
Can you explain me, how finder handles the drag and drop cooperative with OSX, respectively a good Swift 3 manual for “Outside my app drag-and-drop-listener”.
Like your tutorial, I found many excellent explained tutorials for drag and drop. Also I found a Objective-C tutorial for that from apple, wich I was not able to translate to Swift 3, what I tried.
By the way, thanks for your very quick answer, so far and I’m extremely curiousing to your next answer!
Finder is just an application like any other with respect to Drag and Drop. It registers itself with macOS for certain types and vends certain types when you drag from it just like you did in the tutorial.
If you have a look at https://www.raywenderlich.com/165853/menus-popovers-menu-bar-apps-macos it describes how to set up a global event monitor on NSEvent in the EventMonitor class. You might be able to set up a listener to dragging events and from there check the Dragging Pasteboard (NSDragPboard), which is one of the global named pasteboards for content. This might work , or not. I dont know. Its what i’d explore if I was trying to do this.