NSPasteboard and Dynamic UTIs
When dealing with pasted or dragged data in Cocoa, we receive the passed data on an
NSPasteboard. Prior to v10.6 (Snow Leopard), this pasteboard could only contain a single item, but now it can contain multiple items, which are returned as an
NSArray via the
The data passed in via the pasteboard can be in a variety of types (for instance, a single image could be passed both in JPEG format and TIFF format), and for compatibility reasons these types can be expressed in various ways—they can be MIME types, UTIs, OSTypes, or PboardTypes.
If we want to handle pastes and drags in the proper, modern, multi-item fashion, you’d think we could just iterate over the
-pasteboardItems (each is an
NSPasteboardItem), ask each for its
-types, and handle the data in our preferred format. Unfortunately things aren’t always that simple.
Suppose we have an
pboard, which has a single
item) on it. According to the NSPasteboard documentation,
-types method returns “an array of
NSString objects containing the union of the types of data declared for all the pasteboard items on the receiver”.
Therefore, in this simple case,
[pboard types] should return exactly the same types as
[item types]. It turns out that it doesn’t.
Here’s a sample code snippet:
for (NSString* type in [pboard types]) NSLog(@"Pasteboard type: %@", type); NSPasteboardItem* item = [[pboard pasteboardItems] objectAtIndex:0]; for (NSString* type in [item types]) NSLog(@"Item type: %@", type);
…which surprisingly prints the following when a URL is dropped onto it from Safari:
Pasteboard type: dyn.ah62d4rv4gu8zs3pcnzme2641rf4guzdmsv0gn64uqm10c6xenv61a3k Pasteboard type: WebURLsWithTitlesPboardType Pasteboard type: dyn.ah62d4rv4gu8yc6durvwwaznwmuuha2pxsvw0e55bsmwca7d3sbwu Pasteboard type: Apple URL pasteboard type Pasteboard type: public.url Pasteboard type: CorePasteboardFlavorType 0x75726C20 Pasteboard type: public.url-name Pasteboard type: CorePasteboardFlavorType 0x75726C6E Pasteboard type: public.utf8-plain-text Pasteboard type: NSStringPboardType Item type: dyn.ah62d4rv4gu8zs3pcnzme2641rf4guzdmsv0gn64uqm10c6xenv61a3k Item type: dyn.ah62d4rv4gu8yc6durvwwaznwmuuha2pxsvw0e55bsmwca7d3sbwu Item type: public.url Item type: public.url-name Item type: public.utf8-plain-text
This might be OK if you’re looking for one of the types that is showing up as an “Item type”, but what if you are expecting one of those that isn’t?
Notice that there are exactly twice as many types in
[pboard types] as there are in
[item types], and that all of the item types share a common dot-separated format. This is our hint that there’s some correspondence here.
Apple’s intention for developers is that we move towards using only UTI-type identifiers in the future, and it seems that in the jump to the multi-item pasteboard they’ve silently dropped support for other types of identifiers. The exception to this rule is when using the old, single-item-style
NSPasteboard methods (
setData:forType:, etc.). These methods likely continue to exist purely for the sake of old code, but now implicitly operate on the first item on the pasteboard.
Since we only see UTIs when dealing with
NSPasteboardItems directly, how are we to retrieve data an older application placed on the pasteboard if that data is not identified with a UTI? It turns out that’s what those
dyn.(...) types are for—they’re dynamically-generated UTIs which correspond to type identifiers in different formats.
In the example above,
dyn.ah6(...)a3k is the UTI way of identifying data in the
WebURLsWithTitlesPboardType type, and
dyn.ah6(...)bwu is the UTI for
Apple URL pasteboard type.
(If you’re wondering,
public.utf8-plain-text are also UTIs, but they’re UTIs that are declared to correspond to the other types you see in the output, so they don’t need to be dynamically-generated. The basic UTI types are declared in the
LaunchServices/UTCoreTypes.h header, and an application can declare its own types in its
Info.plist file under the
If you’re looking to cleanly handle data that an application placed on the pasteboard which is not identified by a UTI, you need to look up the dynamic UTI which corresponds to the identifier you have. In order to get the right UTI you’ll need to know not only the identifying string for the type, but also what kind of type it is (MIME, PboardType, etc.). These different kinds are represented by the
kUTTagClassOSType constants in
LaunchServices/UTType.h. Once we have this info we simply need to pass it to
UTTypeCreatePreferredIdentifierForTag() to get the UTI.
For instance, let’s say we take a look at the
-types, and we see that it’s passing in data in the
WebURLsWithTitlesPboardType format. We want that data, but we’re iterating over the
pasteboardItems, so we need the corresponding UTI. We can get it like so:
CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassNSPboardType, CFSTR("WebURLsWithTitlesPboardType"), kUTTypeData);
If we’d run this code in the process above,
uti would now contain
That’s it! Now we have a UTI suitable for comparing against the
Note: Despite what the documentation suggests, passing
NULL as the third argument to
UTTypeCreatePreferredIdentifierForTag() does not return the correct dynamic UTI. You must be more specific and pass
I owe thanks to SIGPIPE 13’s UTI Problems post for the last bit about
kUTTypeData (I’m not sure I’d ever have caught that one my own), and the fine folks at StackOverflow who gave me enough hints to puzzle the rest out.