Handling Dock Icon Drags From iTunes
• Chris Liscio
Over the past few days, I spent more time than should have been necessary trying to drag songs from iTunes to my application's dock icon. There is already code out there to help folks handle a drag from iTunes to a custom NSView, but nothing has ever been said about handling a drag from iTunes to your application's dock icon.
One might think that because you can drag a song out of iTunes into the Finder, and have the file copied there, that you can simply publish support for the public.audio UTI and everything will work fine. Of course, life is not so simple.
A drag out of iTunes puts a few different flavours of data onto the pasteboard—none of which appear to be natively accepted by the dock. The most intriguing of these data items is the one with the 'itun' OSType. It is an XML property list that can be stuffed into an NSDictionary and then read from—this is how folks currently access the song's location in their drag handlers (see the code link above).
Now, in order to support dragging a song from iTunes to your application's dock icon, life gets somewhat more complicated…
First of all, to handle a pasteboard drag to your application, you must expose a service in your application's Info.plist. Check out Will Larson's blog post about handling text drags for more information about how to do this. I started from this point.
One might make the immediate conclusion that you can simply add
CorePasteboardFlavorType 0x6974756E to your list of NSSendTypes in your service definition. I did, and I was wrong—neither of these two things will cause your dock icon to accept the drag.
So, after a lengthy discussion with some other developers, I determined a bittersweet workaround. In order for this to work properly, I need you to promise me that you will do exactly as I prescribe in order to accept iTunes dock drags. So, pay close attention.
Because NSSendTypes accepts a list of NSPboardTypes or UTIs, and not OSTypes, we will have to wrap the OSType in a UTI. Unfortunately, we can't all go around wrapping
itun in different UTIs, because this will not work. The first UTI to claim
itun will win out over the others, and only one application will accept these dragged songs from iTunes.
So, the ideal situation would be to wait for Apple to expose a UTI from the iTunes Info.plist, but then we would all die holding our breath. Instead, I'm asking you to define this small chunk of the Info.plist for your application.
Yep, I've gone and named it after myself—
org.liscio.itun. That way, you all know where it came from. I can't go around writing into the public.* domain, or the com.apple.* domain, so I didn't. Using org.* instead seemed more community-oriented. :)
After you've imported this type, add
org.liscio.itun to your list of NSSendTypes, and you're done. Your application should now accept song drops from iTunes. (See below for some troubleshooting tips.)
Note that you're importing this type definition in your plist, and not exporting it. In fact, all my apps will be importing it as well. Nobody should really own it, as it doesn't belong to any of us. If Apple ever does decide to add a UTI to wrap the 'itun' type, then we'll all have to change our imported type definition accordingly. I'm OK with that, and it's really a simple thing to fix…
I hope this helps you folks give your users a better experience in your apps, and I certainly don't mind a "digital high-five" in your About box if it's helped you as well. You can even drop me a line to let me know that you're going to use it. This also serves the purpose in my potentially letting you know if Apple does actually add this UTI, and it's time to remove my UTI from your plist. ;)
I don't expect this to happen to most of you if you get it right from the get-go, but I figured I might as well add this just in case…
The above change to your Info.plist may not take effect right away. You might have to kick the services system using
NSUpdateDynamicServices() (via Ruby or Python is best), quit iTunes, or use some magic incantation of
lsregister at the terminal. Sometimes you might even have to reboot and try again. This is a part of life when you're messing around with UTIs and services during development, unfortunately.
If you're still having trouble, first try to make sure you can register a simple service using the NSStringPboardType, and that you get called in your app delegate first. Don't even think of asking me for help until you've verified this… ;)
I filed a radar, rdar://6616686 for this issue, so it's tracked appropriately by Apple. I also added it to OpenRadar for all to see: http://openrdar.com/6616686