iTunes Tutorial for iOS: How To Integrate iTunes File Sharing With Your iOS App

An iTunes tutorial on how to let users easily copy files to and from your app using the iTunes File Sharing feature. By Ray Wenderlich.

Leave a rating/review
Save for later
Share

Freak out your friends by sending them bugs!

Freak out your friends by sending them bugs!

Freak out your friends by sending them bugs!

The iPad and iOS 4 have a great new feature called File Sharing that provides a convenient way for users to transfer files between their computer and your app.

But figuring out exactly what you need to do to get this working in real-world scenarios can be tricky. So I thought it would be useful to write an iTunes tutorial that covers how to do that with a real app, step by step!

If you’ve been following the development of the “Scary Bugs” app in the simple app tutorial or NSCoding tutorial, it’s all been leading up to this!

In those tutorials, we’ve been creating an app that allows you to collect scary bugs and rate them. However, it would be even better if users could share bugs with their friends (or mortal enemies!)

So let’s do it! You’ll need a copy of the ScaryBugs project where we last left off – if you don’t have it already, you can grab a copy here.

File Sharing Overview

Let’s start with an overview with how File Sharing works.

To enable File Sharing in your app, you simply set the boolean flag “UIFileSharingEnabled” in your info.plist.

iTunes will then display anything you save to the Documents directory in your app to the user, when they go to the “Apps” page in iTunes and scroll to the bottom:

iTunes File Sharing Screenshot

Of course, for this to be of any use, your app needs to have some smarts in it.

First, your app needs to be able to detect the files that the user puts into this directory.

Second, your app needs to be able to deal with the fact that the contents of this folder can change at any time. The user can rename files, delete files, or even put garbage in there if they want.

Third, your app needs to have a way to export your app’s document as a single file, even if it consists of multiple files.

“But wait!”, you may say. “What about packages, can’t I just set up my app to use a Document Package and have my folder of files be treated like a single file?”

Well, about that…

File Sharing and Document Packages

If you want to skip this section and move on, the TLDR summary is: Document Packages don’t work the way you’d expect with iTunes currently, so you need to use an alternative solution.

But if you’re curious about them and why they don’t work, here’s some more information.

For those of you who are unfamiliar, packages are a way that iOS and MacOS can treat a directory of files as a single file. This is used for .apps – these are actually directories of files, with a standard structure (a “bundle”).

Many apps on the Mac use store their documents as packages – Document Packages to be specific. Again, these are simply directories with whatever makes up the app’s document contents inside, often with a particular file extension.

You can register your app as the owner of files with a particular extension in your info.plist, and mark the type as a package with the LSTypeIsPackage key. If you do this on the Mac and install your app to your Applications folder, the OS will detect it and start treating any directory with your registered extension as a package.

You can register your iOS app as being the owner of a file type/extension that is a package as well. If you save it to the Documents directory, you might think that it will show up in File Sharing as a single file rather than a directory.

Well it does – but it doesn’t work exactly the way you’d think. It will show up in iTunes as a single file just fine:

File Sharing Document Packages from iPhone

However, when you save the package to disk, it will be saved as a folder, not as a single file:

File Sharing Document Packages from iPhone on Mac

To make it worse, there’s no way for the user to import the folder back into iTunes, and they can mess around with the contents of the folder.

You could get the package to show up as a single file if you also had a companion app installed on the Mac, but most of the time as app developers we won’t be able to guarantee that the user has the companion app installed as well. Plus, this wouldn’t work at all on a PC.

So as I said, document packages just aren’t the way to go with File Sharing at this point. “But wait!”, you may say. “What about Pages and other Apple iPad apps, don’t they use packages just fine?”

Well, about that…

File Sharing and Pages

If you’re curious how Apple handles things, if you don’t have Pages you might want to check out this great article with a summary of the File Sharing process with Pages.

But as a quick summary, here’s how I understand Pages to work:

  • The Pages documents aren’t actually packages, they are zipped packages so they can be treated as a single file.
  • Pages actually has two separate lists of documents:
    • The list of documents that Pages uses, in a private directory not available to the user.
    • The list of documents that is available for File sharing, in the Documents directory.
  • The user has to actually take a step to import or export a document to/from File Sharing (it doesn’t just automatically show up). I believe this is because it would be a performance penalty to constantly create zipped copies of documents mirroring the actual documents.

So, in our case with Scary Bugs, we are in a similar situation since we have documents made up with several files. Therefore, we are going to take Apple’s approach, and have manual steps to import/export files to File Sharing, and export our documents as zipped directories.

Importing and Exporting Our Documents

Ok enough talk, time for action! First things first – we’re going to use some helper code from the users at CocoaDev.com for gzip/gunzip. So grab a copy, and drag the two files into the Helpers group of your project.

Also, while you’re at it, these files require zlib, so go to your project settings and add “-lz” to your “Other Linker Flags”.

Now onto the code! Let’s start by adding the code to our ScaryBugDoc to support exporting and importing our documents as a zipped file that we can share with File Sharing.

First make the following mods to ScaryBugDoc.h:

// After @interface
- (NSString *)getExportFileName;
- (NSData *)exportToNSData;
- (BOOL)exportToDiskWithForce:(BOOL)force;
- (BOOL)importFromPath:(NSString *)importPath;

Just declaring some methods we’re about to implement here. Switch over to ScaryBugDoc.m and let’s implement them one by one:

1) Implement getExportFileName

// Add to top of file
#import "NSData+CocoaDevUsersAdditions.h"

// Add new method
- (NSString *)getExportFileName {
    NSString *fileName = _data.title;
    NSString *zippedName = [fileName stringByAppendingString:@".sbz"];
    return zippedName;
}

This method will return the name that we’re going to export our bug as. We don’t want to export the bug with a simple numeric directory name like our documents are named internally, because then our user will have no good way of knowing what’s inside. So instead, we use the title of the bug to construct the filename.

Speaking of which, I’m not sure if there’s an easy way to massage the filename so it doesn’t contain any unsupported characters on both Mac and Windows, anybody know a solution to that?

The other thing to note is that we end the filename with an extension “.sbz” which we’ll register our app as being able to open later. When I was first playing around with this, I tried using a double extension such as “.scarybug.gz”, but when I was trying to open the attachment from Mail, it would never launch my app, and I suspect it didn’t like the double extension. So I recommend using just a single extension for now.

2) Implement exportToNSData

So now we need a method to take our directory and convert it into a single buffer of NSData so we can write it out to a single file.

There are different ways to do this – one popular way is to zip the directory up using the open source ZipArchive library. Another popular way is to use a combination of tar and gzip code. But I thought I’d show you another way: using NSFileWrapper to serialize the data, then gzipping that up.

- (NSData *)exportToNSData {
    NSError *error;
    NSURL *url = [NSURL fileURLWithPath:_docPath];
    NSFileWrapper *dirWrapper = [[[NSFileWrapper alloc] initWithURL:url options:0 error:&error] autorelease];
    if (dirWrapper == nil) {
        NSLog(@"Error creating directory wrapper: %@", error.localizedDescription);
        return nil;
    }   
    
    NSData *dirData = [dirWrapper serializedRepresentation];
    NSData *gzData = [dirData gzipDeflate];    
    return gzData;
}

NSFileWrapper is a new class available in iOS4+ that among other things provides an easy way to serialize entire directory contents. As you can see it’s pretty simple to use here: we just initialize it with a URL, then we can get an NSData representation of a directory by calling serializedRepresentation.

This isn’t compressed, so we use the gzipDeflate helper method from the NSData extensions we downloaded earlier to do that.

3) Implement exportToDiskWithForce

- (BOOL)exportToDiskWithForce:(BOOL)force {
 
    [self createDataPath];
      
    // Figure out destination name (in public docs dir)
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];      
    NSString *zippedName = [self getExportFileName];
    NSString *zippedPath = [documentsDirectory stringByAppendingPathComponent:zippedName];
    
    // Check if file already exists (unless we force the write)
    if (!force && [[NSFileManager defaultManager] fileExistsAtPath:zippedPath]) {
        return FALSE;
    }
    
    // Export to data buffer
    NSData *gzData = [self exportToNSData];
    if (gzData == nil) return FALSE;
    
    // Write to disk
    [gzData writeToFile:zippedPath atomically:YES];
    return TRUE;
    
}

The first thing we do here is construct the full path to where we’re going to save our zipped document. Note this time we’re saving in the Documents directory (not Library\Private Data), because the Documents directory is what’s available for File Sharing.

Next we check to see if the file is already there. If it is, we’re going to want to present a warning to the user, so we return FALSE unless the user forces the save.

Finally, we just make a call to export it as NSData, and simply write it out to the disk.

4) Implement importFromPath

- (BOOL)importData:(NSData *)zippedData {
    
    // Read data into a dir Wrapper
    NSData *unzippedData = [zippedData gzipInflate];                
    NSFileWrapper *dirWrapper = [[[NSFileWrapper alloc] initWithSerializedRepresentation:unzippedData] autorelease];
    if (dirWrapper == nil) {
        NSLog(@"Error creating dir wrapper from unzipped data");
        return FALSE;
    }
    
    // Calculate desired name
    NSString *dirPath = [ScaryBugDatabase nextScaryBugDocPath];                                
    NSURL *dirUrl = [NSURL fileURLWithPath:dirPath];                
    NSError *error;
    BOOL success = [dirWrapper writeToURL:dirUrl options:NSFileWrapperWritingAtomic originalContentsURL:nil error:&error];
    if (!success) {
        NSLog(@"Error importing file: %@", error.localizedDescription);
        return FALSE;
    }
        
    // Success!
    self.docPath = dirPath;
    return TRUE;    
    
}

- (BOOL)importFromPath:(NSString *)importPath {
    
    // Read data into a dir Wrapper
    NSData *zippedData = [NSData dataWithContentsOfFile:importPath];
    return [self importData:zippedData];
    
}

We’re actually going to extract most of the work from importFromPath into a helper function called importData, because it will be useful later.

In importData, we just do the opposite of what we did in exportData – we inflate the zipped contents and use NSFileWrapper to expand it again with writeToURL:options:originalContentsURL:error.

As for the destination file name, we just create the next available file name. So we’re never overwriting an existing file when you import, we always create a new file. This is by design to avoid the user from accidentally overwriting their files. If they import the same file twice, they’ll have a duplicate, but they can easily delete files.

Ok – that’s it for the core code. There’s still a bit more to do to integrate into the rest of the app though – we have to make some mods to the ScaryBugDatabase and add some GUI elements to support this.