- Working with Uniform Type Identifiers
- Accessing the System Pasteboard
- Recipe: Passively Updating the Pasteboard
- Recipe: Enabling Document File Sharing
- Recipe: Monitoring the Documents Folder
- The Document Interaction Controller
- Recipe: Checking for the Open Menu
- Declaring Document Support
- Recipe: Implementing Document Support
- Exported Type Declarations
- Creating URL-Based Services
- Recipe: Adding Custom Settings Bundles-Or Not
- Summary
Recipe: Implementing Document Support
If your application provides document support, each time it becomes active, you should check to see if an Inbox has appeared in the Documents folder. If so, you should move elements out of that inbox and to where they belong, typically in the main Documents directory. Once the inbox has been cleared, delete it. This provides the best user experience, especially in terms of any file sharing through iTunes where the Inbox and its role may confuse users.
- (void)applicationDidBecomeActive:(UIApplication *)application { // perform inbox test here }
When moving items to Documents, you’ll want to check for name conflicts, and use an alternative path name (typically by appending a hyphen followed by a number) to avoid overwriting any existing file. The following method helps find an alternative name for a destination path. It gives up after a thousand attempts, but seriously, none of your users should be hosting that many duplicate document names. If they do, there’s something deeply wrong with your overall application design.
- (NSString *) findAlternativeNameForPath: (NSString *) path { NSString *ext = path.pathExtension; NSString *base = [path stringByDeletingPathExtension]; for (int i = 0; i < 999; i++) { NSString *dest = [NSString stringWithFormat:@"%@-%d.%@", base, i, ext]; // if the file does not yet exist, use this destination path if (![[NSFileManager defaultManager] fileExistsAtPath:dest]) return dest; } NSLog(@"Exhausted possible names for file %@. Bailing.", path.lastPathComponent); return nil; }
Recipe 16-5 walks through the ugly details of scanning for the Inbox and moving files into place. It removes the Inbox once it is emptied. As you can see, any method like this is file manager-intensive. It primarily involves handling all the error combination possibilities that might pop-up throughout the task. This should run quickly for small file support. If you must handle large files, such as video or audio, make sure to perform this processing on its own operation queue.
If you plan to support public.data files (i.e., will open anything), as this recipe does, you may want to display those files using UIWebView instances. Refer to Technical Q&A QA1630 (http://developer.apple.com/library/ios/#qa/qa1630) for details about which document types iOS can and cannot display in those views. Web views can present most audio and video assets, as well as Excel, Keynote, Numbers, Pages, PDF, Powerpoint, and Word resources in addition to simple HTML.
Recipe 16-5. Handling Incoming Documents
- (void) cleanInboxIfNeeded { NSString *documentsPath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"]; NSString *inboxPath = [documentsPath stringByAppendingPathComponent:@"Inbox"]; // Does the Inbox exist? If not, we're done BOOL isDir; if (![[NSFileManager defaultManager] fileExistsAtPath:inboxPath isDirectory:&isDir]) return; NSError *error; BOOL success; // If the Inbox is not a folder, remove it. if (!isDir) { success = [[NSFileManager defaultManager] removeItemAtPath:inboxPath error:&error]; if (!success) { NSLog(@"Error deleting Inbox file (not directory): %@", error.localizedFailureReason); return; } } // Retrieve a list of files in the Inbox NSArray *fileArray = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:inboxPath error:&error]; if (!fileArray) { NSLog(@"Error reading contents of Inbox: %@", error.localizedFailureReason); return; } // Remember the number of items NSUInteger initialCount = fileArray.count; // Iterate through each file, moving it to Documents for (NSString *filename in fileArray) { NSString *source = [inboxPath stringByAppendingPathComponent:filename]; NSString *dest = [documentsPath stringByAppendingPathComponent:filename]; // Is the file already there? BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:dest]; if (exists) dest = [self findAlternativeNameForPath:dest]; if (!dest) { NSLog(@"Error. File name conflict not resolved"); continue; } // Move file into place success = [[NSFileManager defaultManager] moveItemAtPath:source toPath:dest error:&error]; if (!success) { NSLog(@"Error moving file from Inbox: %@", error.localizedFailureReason); continue; } } // Inbox should now be empty fileArray = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:inboxPath error:&error]; if (!fileArray) { NSLog(@"Error reading contents of Inbox: %@", error.localizedFailureReason); return; } if (fileArray.count) { NSLog(@"Error clearing Inbox. %d items remain", fileArray.count); return; } // Remove the inbox success = [[NSFileManager defaultManager] removeItemAtPath:inboxPath error:&error]; if (!success) { NSLog(@"Error removing inbox: %@", error.localizedFailureReason); return; } NSLog(@"Moved %d items from the Inbox", initialCount); } // Check for the Inbox each time the app becomes active - (void)applicationDidBecomeActive:(UIApplication *)application { [self cleanInboxIfNeeded]; }