Thursday, August 5, 2010

How to get the file extension for a mime type on iOS

I was working on some code that downloads and saves images from the web to the iPhone and iPad, and wanted to use the correct extension on the filename. In this case, the image is coming from a URL like http://example.com/apps/mycoolapp/icon, where a web application is returning the image data dynamically, so I can't use the extension from the filename part of the URL.

I could just save the image data in a file named "icon" with no extension, since the UIImage class will look at the header in the image data to determine the true image type. Neither the filename extension nor the mime type in the Content-Type response header is reliable in general, but in this case I wrote the web application that my iPhone app is talking to, so I can make sure the Content-Type in the response is reliable.

So now all I need is a way to turn a mime type into a file extension. Rather than build my own table that maps mime types to extensions, I though there might be a way to get this from iOS. Turns out there is: Uniform Type Identifiers, which are available on both iOS and Mac OS X. On iOS, first add the MobileCoreServices framework to your project, then import the <MobileCoreServices/MobileCoreServices.h> header. On Mac OS X, add the CoreServices framework and import <CoreServices/CoreServices.h>

A Uniform Type Identifier (UTI) is simply a string like public.png or com.apple.pict that identifies a file or directory type. Proprietary formats use a reverse DNS naming scheme while common formats begin with public. Additionally, third party developers can define UTIs for their application data formats. UTIs are typically associated with one or more file extensions and mime types. In UTI parlance, extensions and mime types are "tags".

If you have a mime type, you can get the UTI for it using the UTTypeCreatePreferredIdentifierForTag() function. To get the UTI for image/png:
// get a UTI for a mime type
CFStringRef mimeType = (CFStringRef)@"image/png";
CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType, NULL);
// uti now equals "public.png"
Since this is a procedural API, Core Foundation memory management rules are in effect. You are the owner of the CFStringRef returned by UTTypeCreatePreferredIdentifierForTag() and are responsible for calling CFRelease() on it. Alternately, since CFStringRef is toll-free bridged to NSString, you can cast the returned CFStringRef to a NSString* and call the -release or -autorelease method.

Once you have the UTI, you can get the extension for it. The UTTypeCopyPreferredTagWithClass() function takes a UTI and returns the first "tag" of the desired class (extension or mime type):
// get an extension for a UTI
CFStringRef extension = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassFilenameExtension);
As with UTTypeCreatePreferredIdentifierForTag(), you own the CFStringRef returned by UTTypeCopyPreferredTagWithClass() and must CFRelease() it or cast it to NSString* and -release or -autorelease it.

You can easily reverse this process by switching the tag class constants kUTTagClassMIMEType and kUTTagClassFilenameExtension in the examples. There's a little bit more to UTIs than I've covered here. The Uniform Type Identifiers Overview explains all the concepts behind UTIs and tags. The UTType Reference details all the functions and constants. Finally, Uniform Type Identifiers Reference lists all the system defined UTIs and their associated tags.

1 comment:

Sandy said...

Thanks for that - especially the "" part - the Apple documentation is wrong on which include file to use, leading to a "expected declaration specifiers or '...' before 'CFXMLTreeRef'" error. Changing the include to fixed the problem.