iTunes File Sharing

iTunes File Sharing

For this weeks blogpost, we were trying to figure out how iTunes file sharing works.
Basically, it’s “just flipping the switch”, which means that if you set the BOOL, called Application supports iTunes file sharing, in your app’s plist to YES, it should work.
But what comes after that? How does your app handle those documents? Let's find out!

After you enable file sharing, your app will already be available within the iTunes applications tab for File sharing!

But what happens when you drag a file, for example an image, to your app?

It all depends on what you want to do with it! In the example I explain here, the goal is to be able to drag and drop images by using the iTunes file sharing option, and use these images within my application in a tableview, so the user could see, use and drag & drop them. Instead of downloading the images from the internet on the device and save them locally, by using iTunes sharing you can just drag and drop these images from your mac's local file system on to the application.

On Application startup, you can do some easy checks, like this:

- (void) checkOrCopyImages {

  // Create an array where you will store the files copied in your document

  NSMutableArray *result = [[NSMutableArray alloc] init];

  // Check the path of your document Directory, where your iTunes file sharing copies to
  NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

  NSString *publicDocumentsDir = [paths objectAtIndex:0];

  NSError *error;

  // Put all the files that are currently in the documents folder in the files array
  NSArray *files = [[NSFileManager defaultManager] 
      contentsOfDirectoryAtPath:publicDocumentsDir 
                          error:&error];

  if (files == nil) {
    // Something went wrong here, localizedDescription gives a good feedback about this error 

    NSLog(@"Error reading contents of documents directory: %@", [error localizedDescription]);
  } else {

    for (NSString *file in files) {

      // Check each file in files (these are filepaths), and check if they 
      // are a PNG or jpg (case insensitive for JPG/jpg, PNG/png) 

      if ([file.pathExtension compare:@"png" options:NSCaseInsensitiveSearch] == NSOrderedSame || 
          [file.pathExtension compare:@"jpg" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
          // If they are a png/jpg, copy their full file path to our retval array from the beginning

         NSString *fullPath = [publicDocumentsDir stringByAppendingPathComponent:file];
         [result addObject:fullPath];
      }
    }
  }

  // Now for each file path in the documents directory, we're going to 
  // check if they are really a PNG/JPG again, to be sure

  for (int i = 0; i < [retval count]; i++) {

    NSString *file = [retval objectAtIndex:i];
    NSURL *url = [NSURL fileURLWithPath:file];

    if ([file.pathExtension compare:@"png" options:NSCaseInsensitiveSearch] == NSOrderedSame || 
        [file.pathExtension compare:@"jpg" options:NSCaseInsensitiveSearch] == NSOrderedSame) {

      // So, they are really a PNG/JPG image, in our case, we want to 
      // copy the files to our Library for further use 

      NSError *error2 = nil;

      NSArray *libPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
      NSString *libraryDirectory = [libPath objectAtIndex:0];

      // Here we only want the real image name + extension to save, so not the full URL path

      NSString *imageNameStart = [result objectAtIndex:i];

      NSArray *imagePathArray = [imageNameStart componentsSeparatedByString:@"/"];

      // Here we are adding the string again to make an URL for copying

      NSString *iconpath = [libraryDirectory stringByAppendingFormat:
                 [NSString stringWithFormat:@"/%@",[imagePathArray lastObject]]];

      NSURL *iconURL = [NSURL fileURLWithPath:iconpath];

      // Does the file exists at the iconpath (in our library, if it does,
      // don't add it again, so duplicate file names aren't possible

      if (![[NSFileManager defaultManager] fileExistsAtPath:iconpath]) {

        // Didn't exist yet, so we're going to try to copy from our Documents to our Library */

        BOOL success = [[NSFileManager defaultManager] copyItemAtURL:url 
                                                               toURL:iconURL 
                                                               error:&error2];
        if (!success) {
           NSLog(@"error2: %@", [error2 localizedDescription]);
        }
      }
    }
  }
}

Now you have the images stored in your NSLibrary, but now you want to use them in a tableview or a Gridview.

So what now? Normally you would do this:

[UIImage imageNamed:@”imageName.png”];

But this convenience method will only "scan" the Project folder, and not the Documents/Library folder.

Whenever you need an array or specific image from this folder, you should do this:

NSString *filePath = [NSString stringWithFormat:@"%@/%@",
                     [self applicationLibraryDirectory], 
                     [_imageNames objectAtIndex:index]];

if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {

  UIImage *newImage = [UIImage imageWithContentsOfFile:filePath];

- (NSString *)applicationLibraryDirectory {
   return [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
}

And here the last question, how does it work the other way around? I want to copy something to the documents folder so I can drag it OUT of itunes on to my local file system. Well, it’s nearly the same process but you just have to switch the URL’s.

- (IBAction)downloadSound:(id)sender {

  NSString *soundPath = @”SomeSound”;

  NSString *mp3file = [NSString stringWithFormat:@"%@/%@.%@", 
                        [[NSBundle mainBundle] resourcePath], soundPath, @"mp3"];
  NSURL *url = [NSURL fileURLWithPath:mp3file];

  if ([mp3file.pathExtension compare:@"mp3" options:NSCaseInsensitiveSearch] == NSOrderedSame) {

    // Here we only want the real sound name + extension to save, so not the full URL path

    NSArray *soundPathArray = [mp3file componentsSeparatedByString:@"/"];

    // Here we are adding the string again to make an URL for copying

    NSString *soundPath = [[self applicationDocumentDirectory] stringByAppendingFormat:
                       [NSString stringWithFormat:@"/%@",[soundPathArray lastObject]]];

    NSURL *soundURL = [NSURL fileURLWithPath:soundPath];

    NSError *error = nil;

    if (![[NSFileManager defaultManager] fileExistsAtPath:soundPath]) {

      // Didn't exist yet, so we're going to try to copy it to our Documents folder 

      BOOL success = [[NSFileManager defaultManager] copyItemAtURL:url 
                                                             toURL:soundURL
                                                             error:&error];

      if (!success) {
        NSLog(@"error: %@", [error localizedDescription]);
      }
    } else {

      UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Sorry" 
                message:@"This sound is already in your downloads folder" 
               delegate:self cancelButtonTitle:nil otherButtonTitles:@"Ok", nil];
      [alert show];
    }
}

And that's about it!

If you have any more questions, go ahead and ask down below in the comment section :)