How to pass a document from one app to another

This post will cover some of interoperability questions in iOS. Many business apps deal with documents (or files). Sandboxing approach of iOS prevents app from just browsing the file system and looking for files. However, sometimes app just needs to be able to pass file to another app. There is an app API for that.

Let’s see what do we need to cover these tasks:

  1. Registering our app as file handler
  2. Handling files passed from other apps
  3. Passing file from our app to other apps
  4. (optional) Previewing files


Let’s start with preparing our app to handle files passed from other apps. To do so, you have to make some changes to your Info.plist file. With this code, we’ll register our app as handler for all document types (we’ll register to base type for the physical hierarchy).

<key>CFBundleDocumentTypes</key>
<array>
	<dict>
		<key>CFBundleTypeName</key>
		<string>All documents</string>
		<key>LSHandlerRank</key>
		<string>Alternate</string>
		<key>LSItemContentTypes</key>
		<array>
			<string>public.item</string>
		</array>
	</dict>
</array>

CFBundleDocumentTypes key (Document Types in Xcode) points to an array of document types your app supports. Each document type entry consists of document type name (CFBundleTypeName key), handler rank – Owner, Alternate, None or Default (LSHandlerRank key) and array of content types (LSItemContentTypes key).

Content types are not MIME types (you can define content type using a MIME type though). List of system-defined and other well-known content types is available. You can define your own content types for your documents using reverse-DNS notation (com.example.document-type).

You should avoid declaring your app supporting all document types, if you only support specific one.

Once you defined supported document types, your app will be visible in “Open in” menu in other apps. So, now it’s time to handle files sent to your app.

First thing you have to know about this process is that file will be copied to Documents/Inbox directory of your application and then, -application:openURL:sourceApplication:annotation: method of your application delegate will be called. Your app will be started, of course, if needed.

If your app had to be started to handle file, -application:didFinishLaunchingWithOptions: will have entries in launchOptions regarding the file sent to your app. However, I’d recommend handle file in -application:openURL:sourceApplication:annotation:, since this method also will be called.

-(BOOL)application:(UIApplication *)application
           openURL:(NSURL *)url
 sourceApplication:(NSString *)sourceApplication
        annotation:(id)annotation
{
    // url parameter now has a path to file in Documents/Inbox
    // it is better to move file to proper location and
    // present user with actions to manage the file

    return YES;
}

Together with URL to a file, your app will also get sourceApplication identifier (without team ID though) and annotation (see below). Your actions in this method will be to move file from Documents/Inbox to a proper location, and handle the file (if you created a viewer application, present the contents of the document, for example).

Take a note, that iOS will not delete files from Documents/Inbox, so it is your responsibility to delete unused files. Also, when copying file to that directory, iOS will incrementally change name of new file, so it will not overwrite existing files.

So, we now can handle documents from other apps. What about presenting that “Open in” menu in your app?
Open In MenuApple provided developers with standard approach on how to send document from one app to another – UIDocumentInteractionController. This view controller gives user an opportunity to open a document in apps installed on his device. As a developer, you can’t get the list of apps, or force user to open document in the specific app. All you can do is to give user a choice.

You can, however, pass some information about your app to other application together with the document you selected. This is called an annotation and it is a property list (combination of NSDictionary, NSArray, NSString, NSNumber,NSDate and some others). This data will be sent as annotation to the app user selected to open the file. If your app is popular enough to make integration with, you can document parameters of this annotation to help other developers to get benefits of opening files from your app.

UIDocumentInteractionController usage is really simple. First, we create it using URL of the document.

self.docInteractionVC = [UIDocumentInteractionController interactionControllerWithURL:self.documentURL];

You can also set properties name, UTI to better define document, however, most of them could be set automatically by URL. Also, you could set annotation with details about the document and your application.

Then you can present user “Open in” or “Options” menu. Difference is that “Open in” menu has only list of applications supporting this document type, while “Options” menu also gives additional options like “Print”, “Quick Look” or “Mail” (attach document to a new mail). It is up to developer to decide which menu to be shown. Menu could be presented from bar button, or from any view. This is important for iPad applications.

[self.docInteractionVC presentOptionsMenuFromBarButtonItem:self.navigationItem.rightBarButtonItem
                                                  animated:YES];
// or
[self.docInteractioVC presentOpenInMenuFromRect:sender.frame 
                                         inView:self.view
                                       animated:YES];

Note, that we have to keep reference to this controller (otherwise, you’ll get EXC_BAD_ACCESS on using some actions from Options menu). Also note, that if there are no applications capable of handling document you provided “Open in” menu will not be shown at all.

You can implement UIDocumentInteractionControllerDelegate protocol methods to determine results of the operation.

Now, some bonuses. UIDocumentInteractionController can also be used to preview documents of some system-known types (consult documentation to determine, which document types are supported). Previewing document requires one more action to be performed. You have to implement at least one method of UIDocumentInteractionControllerDelegate protocol.

-(UIViewController*)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController*)controller
{
    return self.navigationController;
}

This method should return view controller which is currently on-screen. It could be either UINavigationController – then preview will be pushed to navigation stack, or any other controller – then preview will be displayed modally (documentation incorrectly states that cross-dissolve transition will be used, actually, cover vertical transition is used).

Now, let’s display preview.

[self.docInteractionVC presentPreviewAnimated:YES];

Actually, UIDocumentInteractionController here acts as a wrapper around QLPreviewController which has some more control over content you’re trying to display. Visually they are the same. To use QLPreviewController you need to link with QuickLook.framework, implement two methods of QLPreviewControllerDataSource protocol.

-(NSInteger)numberOfPreviewItemsInPreviewController:(QLPreviewController *)controller
{
    return 1;
}

-(id<QLPreviewItem>)previewController:(QLPreviewController *)controller 
                   previewItemAtIndex:(NSInteger)index
{
    return self.documentURL;
}

Note that second method returns object that conforms to QLPreviewItem protocol. NSURL is made conforming to this protocol as well.
And then, present it.

QLPreviewController *vc = [[QLPreviewController alloc] init];
vc.delegate = self;
[self presentViewController:vc animated:YES completion:NULL]l;

As you can see, QLPreviewController is more flexible about content, you can show more than one document.

Some Apple documentation: Document Interaction Programming, System-Declared Uniform Type Identifiers, UIDocumentInteractionController reference, QLPreviewController reference.

Example source code is available on GitHub.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s