Email Tutorial for iOS: How To Import and Export App Data Via Email in your iOS App

Ray Wenderlich
Load Attachments In Your App!

Load Attachments In Your App!

A lot of developers want to be able to share their app data via email. It’s a convenient way for users to send data to each other or between devices – and it may even net you some new customers!

Luckily, this is pretty easy to do on the iPhone – you just have to set a few keys in your Info.plist and handle a few callbacks so the OS can open your app with the URL to import.

So we’ll cover how to do exactly that in this email tutorial!

We’re going to start with the Scary Bugs project we’ve been building starting from the simple app tutorial on up to the file sharing tutorial.

So if you don’t have a copy of the project where we left off already, grab a copy!

Setting Up Your Info.plist

We’ve already laid most of the groundwork we need in order to support sharing our app data via email – we’ve written code to save a copy of our app data as a single file.

So the next thing we need to do is set up our Info.plist to let the OS know that we can handle “Scary Bug Documents”. The way you do this on iOS is by registering your app as able to handle certain UTIs, and exporting any UTIs that are not already known by the system.

In summary, UTIs are just unique identifiers that represent your document, such as “com.raywenderlich.scarybugs.sbz”. There are built-in ones for common document types such as “public.jpeg” or “public.html” as well.

So we’re register our app as being able to handle a UTI we make up for our app, and then we’re going to tell the OS a bit about our UTI, such as what file name extension it uses and what mime-type it’s encoded as in email.

So let’s see it in action! Open up ScaryBugs-Info.plist, and add the following entries:

Info.plist Setup for App Documents

You can read up on what each of these value’s mean in Apple’s UTI guide, but here are the important things to note:

  • The CFBundleDocumentTypes entry says what UTIs our app supports – in this case, the com.raywenderlich.scarybugs.sbz UTI, as an Owner/Editor.
  • The UTExportedTypeDeclaration entry gives some information about com.raywenderlich.scarybugs.sbz, since it isn’t a public UTI. Here we say that any file ending in .sbz or has a mime type of application/scarybugs is that kind of file.

Believe it or not, by setting these keys that’s all it takes for the OS to start sending our app files that end with .sbz. You can test it out by emailing yourself a copy of this sample bug if you want:

Emailed Bug Screenshot

You can hold and tap on the attachment and it will prompt you if you want to open it in Scary Bugs. If you do, it will open up Scary Bugs, but of course it won’t load because we haven’t added the code for that yet! So let’s do that next.

Importing App Data

When Mail or some other app wants to send your app a file, it does so via one of two methods: via application:didFinishLaunchingWithOptions, passing the URL in the UIApplicationLaunchOptionsURLKey, or via application:handleOpenURL.

To understand what happens when, Oliver Drobnick has put together a very handy article with diagrams on what gets called when.

So let’s implement that real quick – it will be easy since we already have much of the supporting code in place. Make the following mods to ScaryBugDoc.h:

// After @interface
- (BOOL)importFromURL:(NSURL *)importURL;

The following to ScaryBugDoc.m:

// Add new function
- (BOOL)importFromURL:(NSURL *)importURL {
    NSData *zippedData = [NSData dataWithContentsOfURL:importURL];
    return [self importData:zippedData];    
}

The following in RootViewController.h:

// After @interface
- (void)handleOpenURL:(NSURL *)url;

And the following in RootViewController.m:

// New method
- (void)handleOpenURL:(NSURL *)url {
    [self.navigationController popToRootViewControllerAnimated:YES];
    ScaryBugDoc *newDoc = [[[ScaryBugDoc alloc] init] autorelease];
    if ([newDoc importFromURL:url]) {
        [self addNewDoc:newDoc];       
    }
}

Finally add the following to ScaryBugsAppDelegate.m:

// Add at end of application:didFinishLaunchingWithOptions
NSURL *url = (NSURL *)[launchOptions valueForKey:UIApplicationLaunchOptionsURLKey];
if (url != nil && [url isFileURL]) {
        [rootController handleOpenURL:url];                
} 
 
// Add new method
-(BOOL) application:(UIApplication *)application handleOpenURL:(NSURL *)url {
 
    RootViewController *rootController = (RootViewController *) [navigationController.viewControllers objectAtIndex:0];
    if (url != nil && [url isFileURL]) {
        [rootController handleOpenURL:url];                
    }     
    return YES;
 
}

This is mostly self-explanatory, but a few notes.

First, in ScaryBugDoc we add a method to import a document from a URL. The URL that is passed into us from the system is actually a copy of the document that is placed somewhere in our app’s directory. So we read it out here as an NSData and pass it to our importData method that we wrote earlier.

In RootViewController, we pop to the root view controller (in case we were on a details view somewhere), create a new doc, and import from the given URL.

In ScaryBugsAppDelegate, in both places where we can receive a URL we look for it, and if it’s a file URL (rather than a query string, might have a tutorial on that later), we notify the root view controller so it can do the import.

So go ahead and compile and run your app, and if all works well you should be able to open up the email attachment and see the bug imported into your app!

Imported Bug

Exporting App Data

That was the hard part – exporting data will be smooth sailing from here.

Make the following changes to EditBugViewController.h:

// Add to the top of the file
#import <MessageUI/MessageUI.h>
 
// Modify EditBugViewController to have two new protocols: UIActionSheetDelegate and MFMailComposeViewControllerDelegate
@interface EditBugViewController : UIViewController <UITextFieldDelegate, RateViewDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIAlertViewDelegate, UIActionSheetDelegate, MFMailComposeViewControllerDelegate> {

Then make the following changes to EditBugViewController.m:

// Replace exportTapped with the following:
- (void)exportTapped:(id)sender {
 
    UIActionSheet *actionSheet = [[[UIActionSheet alloc]
                                   initWithTitle:@"" 
                                   delegate:self 
                                   cancelButtonTitle:@"Cancel" 
                                   destructiveButtonTitle:nil 
                                   otherButtonTitles:@"Export via File Sharing", @"Export via Email", nil] autorelease];
    [actionSheet showInView:self.view];
 
}
 
// Add new methods
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
 
    if (buttonIndex == actionSheet.firstOtherButtonIndex + 0) {
 
        [DSBezelActivityView newActivityViewForView:self.navigationController.navigationBar.superview withLabel:@"Exporting Bug..." width:160];   
        [_queue addOperationWithBlock: ^{
            BOOL exported = [_bugDoc exportToDiskWithForce:FALSE];
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                [DSBezelActivityView removeViewAnimated:YES];
                if (!exported) {
                    UIAlertView *alertView = [[[UIAlertView alloc] 
                                               initWithTitle:@"File Already Exists!" 
                                               message:@"An exported bug with this name already exists.  Overwrite?" 
                                               delegate:self 
                                               cancelButtonTitle:@"Cancel" 
                                               otherButtonTitles:@"Overwrite", nil] autorelease];
                    [alertView show];
                }
            }];
        }]; 
 
    } else if (buttonIndex == actionSheet.firstOtherButtonIndex + 1) {
 
        [DSBezelActivityView newActivityViewForView:self.navigationController.navigationBar.superview withLabel:@"Exporting Bug..." width:160];   
        [_queue addOperationWithBlock: ^{
            NSData *bugData = [_bugDoc exportToNSData];
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                [DSBezelActivityView removeViewAnimated:YES];
                if (bugData != nil) {
                    MFMailComposeViewController *picker = [[[MFMailComposeViewController alloc] init] autorelease];
                    [picker setSubject:@"My Scary Bug"];
                    [picker addAttachmentData:bugData mimeType:@"application/scarybugs" fileName:[_bugDoc getExportFileName]];
                    [picker setToRecipients:[NSArray array]];
                    [picker setMessageBody:@"Check out this scary bug!  You'll need a copy of ScaryBugs to view this file, then tap and hold to open." isHTML:NO];
                    [picker setMailComposeDelegate:self];
                    [self presentModalViewController:picker animated:YES];                    
                }
 
            }];
        }]; 
 
    }
 
}
 
- (void)mailComposeController:(MFMailComposeViewController *)controller
		  didFinishWithResult:(MFMailComposeResult)result
						error:(NSError *)error {
    [self dismissModalViewControllerAnimated:YES];
}

Here we modify the export button to prompt the user whether they want to export via File Sharing as before, or via email.

If the user chooses via email, we use a MFMailComposeViewController to create an email message, and we add ad attachment with our bug data to the message. Pretty simple eh?

One last thing before we can test: right click on Frameworks, click “Add\Existing Framework…” and choose “MessageUI.framework” from the list.

Then compile and run your app, and you should be able to automatically export a scary bug from your app via email!

Emailing Bug

Let’s Have Some Fun!

If you’ve gotten this far, why not have some fun?

Create a scary bug with the app, then email it to me at the email address in the above screenshot, and I’ll add it to this post! Let’s see what freakish creatures we can come up with! :]

Update: Here’s a not-so-scary bug submitted by Alex Hedley! :]

Where To Go From Here?

Here is a sample project with all of the code we’ve developed so far in this tutorial series.

This is as far as my plans go for this Scary Bugs app, but who knows, maybe we’ll come back to it someday?

In the meantime, let me know if you have any questions or comments – and what bugs you think are the scariest! :]

Ray Wenderlich

Ray is an indie software developer currently focusing on iPhone and iPad development, and the administrator of this site. He’s the founder of a small iPhone development studio called Razeware, and is passionate both about making apps and teaching others the techniques to make them.

When Ray’s not programming, he’s probably playing video games, role playing games, or board games.

User Comments

51 Comments

[ 1 , 2 , 3 , 4 ]
  • I am learning to do my own apps how can I get a tutorial to send data by email or text message ios x code how can I get it thanks
    miguel1
  • Please I need the video of the tutorial I am very interested thank you how can I get it
    miguel1
  • Hi Ray:

    Thanks for a great tutorial. I download the code, try import app data using BugsBunny.sbz(size 47kb), receive messages as follows:-

    2013-07-20 23:50:28.873 ScaryBugs[13975:907] Loading bugs from /var/mobile/Applications/C5CED488-00BF-4F40-88F3-148D58215CAA/Library/Private Documents
    2013-07-20 23:50:28.881 ScaryBugs[13975:907] Application windows are expected to have a root view controller at the end of application launch
    2013-07-20 23:55:12.824 ScaryBugs[13975:907] Unable to read RTFD from data:0x0
    2013-07-20 23:55:12.828 ScaryBugs[13975:907] Error creating dir wrapper from unzipped data
    2013-07-20 23:55:41.938 ScaryBugs[13975:907] Unable to read RTFD from data:0x0
    2013-07-20 23:55:41.940 ScaryBugs[13975:907] Error creating dir wrapper from unzipped data
    2013-07-21 00:03:57.607 ScaryBugs[13975:907] Unable to read RTFD from data:0x0
    2013-07-21 00:03:57.610 ScaryBugs[13975:907] Error creating dir wrapper from unzipped data
    2013-07-21 00:04:27.439 ScaryBugs[13975:907] Unable to read RTFD from data:0x0
    2013-07-21 00:04:27.441 ScaryBugs[13975:907] Error creating dir wrapper from unzipped data
    2013-07-21 00:04:51.123 ScaryBugs[13975:907] Unable to read RTFD from data:0x0
    2013-07-21 00:04:51.125 ScaryBugs[13975:907] Error creating dir wrapper from unzipped data
    2013-07-21 00:05:03.339 ScaryBugs[13975:907] Unable to read RTFD from data:0x0
    2013-07-21 00:05:03.342 ScaryBugs[13975:907] Error creating dir wrapper from unzipped data

    Search for "unzipped" message found statement as follows:-
    AppData\ScaryBugs6>grep unzipped ScaryBugDoc.m
    ScaryBugDoc.m:203: NSData *unzippedData = [zippedData gzipInflate];
    ScaryBugDoc.m:204: NSFileWrapper *dirWrapper = [[[NSFileWrapper alloc] initWithSerializedRepresentation:unzippedData] autorelease];
    ScaryBugDoc.m:206: NSLog(@"Error creating dir wrapper from unzipped data");

    Suggestions ? Is this is related to BugsBunny.sbz. Do you know where can find a version that works with app code ?

    Thanks.
    ewood1
  • Question #1
    In the case of importing a file, should we worry about deleting the inbound file at - url - once we process it, or does the system take care of that somehow?

    Question #2
    For the cases where we just want to import standard files as email attachments like .pdf, .doc, .docx, etc. we just need to configure CFBundleTypeDocuments:LSContentItems, right? That is, if we are not exporting these files as an email attachment, only importing, we don't need to configure UTExportedTypeDeclaration?

    Thanks. I get a lot out of you tutorials and glad that I bought them...
    Eric
    esirkin
  • Hey Ray! First, I want to thank you and your team for all your tutorials. You are the best!
    I have a question about that tutorial. Should I follow these steps to import or export my app's sqlite database? It can be done?
    anvc
  • Excellent tutorial Ray. I get everything to work, except when user is openeing the mail in gmail app. The attached file get unknown file type and coonot be opened. Is there something that can be done?
    sjurygg
[ 1 , 2 , 3 , 4 ]

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Vote for Our Next Tutorial!

Every week, we alternate between Gaming and Non-Gaming tutorial votes. This week: Non-Gaming!

    Loading ... Loading ...

Last week's winner: How to Make a Simple 2D Game with Metal.

Suggest a Tutorial - Past Results

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in October: Xcode 6 Tips and Tricks!

Sign Up - October

Our Books

Our Team

Tutorial Team

  • Jean-Pierre Distler
  • Brian Broom

... 52 total!

Update Team

  • Zouhair Mahieddine

... 14 total!

Editorial Team

  • Matt Galloway
  • John Clem

... 22 total!

Code Team

  • Orta Therox

... 3 total!

Subject Matter Experts

  • Richard Casey

... 4 total!