Recommended way to set up an NSImageView drag-and-drop?

With the novice’s usual techniques of searching, piecing together, trial and error, etc, I have managed to implement an NSImageView drag-and-drop, the details of which are described below. My implementation does work as expected. I am submitting this post rather for educational purposes, i.e., to ask if there is a better way of doing things than my solution, which seems somewhat complex. (It seems that in Cocoa, there is more often than not an easier way of doing things.)

My goal was for the image view to accept a drag-and-drop from any the following:

  • an image file of any of the following types: JPEG, PNG, TIFF, GIF, BMP, PICT
  • a folder or volume
  • a symlink or alias pointing to either of the preceding two categories

and to reject any other type of drag-and-drop. With a successful drag-and-drop, the image view should set its itemPath property to the posix path of the dropped item (in the case of a symlink or alias, the posix path of the original item).

I placed an image well NSImageView in my window, then subclassed it as follows:

property NSFileManager : class "NSFileManager"
property |NSURL| : class "NSURL"

script myImageView
	property parent : class "NSImageView"
	property itemPath : ""
	
	on draggingEntered_(sender)
		-- Get the URL of the dragged item
		set theURL to sender's draggingPasteboard()'s readObjectsForClasses_options_({|NSURL|}, missing value)'s lastObject()
		-- If the dragged item is a symlink or alias, get the URL of the original item
		tell theURL
			set {isSymlink, isAliasFile} to {false, false}
			tell its getResourceValue_forKey_error_(reference, current application's NSURLTypeIdentifierKey, missing value)
				try
					set {valueFound, uniformTypeIdentifier} to {first item as integer = 1, "" & second item}
					if valueFound then tell uniformTypeIdentifier to set {isSymlink, isAliasFile} to {it = "public.symlink", it = "com.apple.alias-file"}
				end try
			end tell
			if isSymlink then
				set resolvedPath to NSFileManager's defaultManager()'s destinationOfSymbolicLinkAtPath_error_(its |path|(), missing value)
				tell resolvedPath to if it ≠ missing value then set theURL to |NSURL|'s fileURLWithPath_(it)
			else if isAliasFile then
				set bookmarkData to |NSURL|'s bookmarkDataWithContentsOfURL_error_(it, missing value)
				set theURL to |NSURL|'s URLByResolvingBookmarkData_options_relativeToURL_bookmarkDataIsStale_error_(bookmarkData, current application's NSURLBookmarkResolutionWithoutUI, missing value, missing value, missing value)
			end if
		end tell
		-- Determine if the item should be accepted for drag-and-drop depending on its type
		set shouldAcceptDrag to false
		tell theURL's getResourceValue_forKey_error_(reference, current application's NSURLTypeIdentifierKey, missing value)
			try
				set {valueFound, uniformTypeIdentifier} to {first item as integer = 1, "" & second item}
				set shouldAcceptDrag to valueFound and (uniformTypeIdentifier is in {"public.jpeg", "public.jpeg-2000", "public.png", "public.tiff", "com.compuserve.gif", "com.microsoft.bmp", "com.apple.pict", "public.folder", "public.volume"})
			end try
		end tell
		-- If the item is a valid type, accept entry into the image view in preparation for drag-and-drop, and set the subclass's itemPath property to the item's posix path
		if shouldAcceptDrag then
			set my itemPath to "" & theURL's |path|()
			return current application's NSDragOperationCopy
		else
			return current application's NSDragOperationNone
		end if
	end draggingEntered_
	
	on performDragOperation_(sender)
		-- Finalize the drag-and-drop
		log my itemPath --@@
		return true
	end performDragOperation_
end script

I registered the myImageView subclass for drag-and-drop in the app delegate’s initialize handler as follows:

script myAppDelegate
	property parent : class "NSObject"
	property imageView : missing value -- this is an outlet to the window's image view
	
	on initialize()
		-- Instantiate the image view, and register it for drag-and-drop of an NSURL
		set my imageView to current application's myImageView's alloc()'s init()
		tell my imageView to its registerForDraggedTypes_({"public.file-url"})
	end initialize
	
	on applicationWillFinishLaunching_(aNotification)
	end applicationWillFinishLaunching_
	
	on applicationShouldTerminate_(sender)
		return current application's NSTerminateNow
	end applicationShouldTerminate_
end script

The questions I would like to ask are:
Is there a simpler or more elegant way to resolve a symlink or alias to its original item?
Is there a simpler or more elegant way for the image view to screen dragged items for acceptance or rejection based on their Uniform Type Identifier type?
What other ways might the drag-and-drop be made more optimal?

Hello.

If an NSImageWell suits your needs, then that is far easier, since it takes care of all the paste-board and drag and drop. There should be some easy to grasp examples out there with Objective-C.

Edit

If you pursue using the pastboard, then I recommend building and using the clipboard viewer app, so that you can see what is actually getting into it, while you drag. Finder will give you the filename in text first, even if you asked for the Image, which is something you will have to deal with.

That’s often the case, but usually only when doing something, well, typical. What you’re after is a bit unusual.

For symlinks there’s -stringByResolvingSymlinksInPath and -stringByStandardizingPath. You might be better to check NSURLIsAliasFileKey directly for aliases. But do you really need to resolve them?

You could probably register your drag types more specifically. But I don’t know that it will make much difference.

There’s probably not much less you can do.

Thank you for the suggestions, and apologies for the delay in responding.

McUsrII,

  • I have been using an NSImageWell and agree that it does make drag-and-drop far easier.

Shane,

  • stringByResolvingSymlinksInPath works nicely. Thank you for the heads up. NSString’s capabilities never cease to amaze me.
  • Regarding aliases, I found that dropping an image alias onto the NSImageWell did not update the image to the underlying dereferenced file, and thus the code to dereference the alias.
  • I looked into registering drag types but did not find a capability to refine the screening to the desired specificity of Uniform Type Identifier types I was looking for.
  • Thank you for putting into perspective this first foray into image drag-and-drop.

Incidentally, I found several comments in my searching pointing out that NSImageWell doesn’t retain the file path to the source image file but rather simply retains the NSImage. My NSImageView subclass provides this capability by retaining the image’s file path in its itemPath property. I did make one revision. The original code updates the itemPath property whenever an item is dragged into the NSImageView, whether or not it is dropped. If the user cancels the drop, the itemPath property retains the new path. In order to prevent that unwanted behavior, the new version saves the dragged path in an intermediate candidatePath property and sets the final itemPath property only with a successful drop. Here is the final code if it may be helpful to anyone:


property |NSURL| : class "NSURL"

script myImageView
	property parent : class "NSImageView"
	property itemPath : ""
	property candidatePath : ""
	
	on draggingEntered_(sender)
		-- Get the URL of the dragged item
		set theURL to sender's draggingPasteboard()'s readObjectsForClasses_options_({|NSURL|}, missing value)'s lastObject()
		-- If the dragged item is a symlink or alias, get the URL of the original item
		tell theURL
			set {isSymlink, isAliasFile} to {false, false}
			tell its getResourceValue_forKey_error_(reference, current application's NSURLTypeIdentifierKey, missing value)
				try
					set {valueFound, uniformTypeIdentifier} to {first item as integer = 1, "" & second item}
					if valueFound then tell uniformTypeIdentifier to set {isSymlink, isAliasFile} to {it = "public.symlink", it = "com.apple.alias-file"}
				end try
			end tell
			if isSymlink then
				set resolvedPath to its |path|()'s stringByResolvingSymlinksInPath()
				tell resolvedPath to if it ≠ missing value then set theURL to |NSURL|'s fileURLWithPath_(it)
			else if isAliasFile then
				set bookmarkData to |NSURL|'s bookmarkDataWithContentsOfURL_error_(it, missing value)
				set theURL to |NSURL|'s URLByResolvingBookmarkData_options_relativeToURL_bookmarkDataIsStale_error_(bookmarkData, current application's NSURLBookmarkResolutionWithoutUI, missing value, missing value, missing value)
			end if
		end tell
		-- Determine if the item should be accepted for drag-and-drop depending on its type
		set shouldAcceptDrag to false
		tell theURL's getResourceValue_forKey_error_(reference, current application's NSURLTypeIdentifierKey, missing value)
			try
				set {valueFound, uniformTypeIdentifier} to {first item as integer = 1, "" & second item}
				set shouldAcceptDrag to valueFound and (uniformTypeIdentifier is in {"public.jpeg", "public.jpeg-2000", "public.png", "public.tiff", "com.compuserve.gif", "com.microsoft.bmp", "com.apple.pict", "public.folder", "public.volume"})
			end try
		end tell
		-- If the item is a valid type, set the subclass's intermediate candidatePath property to the item's posix path, and accept entry into the image view
		if shouldAcceptDrag then
			set my candidatePath to "" & theURL's |path|()
			return current application's NSDragOperationCopy
		else
			return current application's NSDragOperationNone
		end if
	end draggingEntered_
	
	on performDragOperation_(sender)
		-- With a successful drop, set the subclass's itemPath property to the item's posix path, and finalize the drag-and-drop
		set my itemPath to my candidatePath -- the path of the dropped item, which is retained by the image view and is accessible to other objects by querying the image view's itemPath property
		return true
	end performDragOperation_
end script

NOTE: This edit incorporates the stringByResolvingSymlinksInPath method.