Saturday, January 21, 2012

Composing Emails and Sharing Files in MonoTouch

Lately, I had to add the ability to import and export data from a MonoTouch application via email. Thanks to everything you get in the iOS SDK for free, this is actually relatively easy. But you do need to know all the places you need to touch to make this happen. You'll need to:

  • Modify your info.plist to specify the file types your application supports
  • Update your application delegate to override the HandleOpenUrl method
  • Update your view controller to show a mail composer
  • Add code to your view controller to import a file that is passed to it and display it

Many thanks to this this tutorial on email sharing to get me going on this.

Details after the break.

The first thing you'll need to do is add some keys to your info.plist. The three top-level keys you'll add will enable iTunes sharing, enable file exporting, and enable file importing, respectively. Pay special attention to the mime-type, because you'll need to re-use the value you put in here later in code. Also, the mime-type template I put in there is for a vendor-specific format. If you're doing plain old XML, you could just use "text/xml".

...
<key>UIFileSharingEnabled</key>
<true/>
...
<key>UTExportedTypeDeclarations</key>
<array>
    <dict>
        <key>UTTypeConformsTo</key>
        <array>
            <string>public.data</string>
        </array>
        <key>UTTypeDescription</key>
        <string>Your Document Type</string>
        <key>UTTypeIdentifier</key>
        <string>com.yourcompany.yourtype</string>
        <key>UTTypeTagSpecification</key>
        <dict>
            <key>public.filename-extension</key>
            <string>ext</string>
            <key>public.mime-type</key>
            <string>application/vnd.yourcompany.yourtype</string>
        </dict>
    </dict>
</array>
...
<key>CFBundleDocumentTypes</key>
<array>
    <dict>
        <key>CFBundleTypeName</key>
        <string>Your Document Type</string>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>LSHandlerRank</key>
        <string>Owner</string>
        <key>LSItemContentTypes</key>
        <array>
            <string>com.yourcompany.yourtype</string>
        </array>
    </dict>
</array>
...

You'll need to add one method to your application delegate to handle URLs that iOS passes to your application to be imported:

public class MyAppDelegate : UIApplicationDelegate
{
    MyViewController viewController;

    ...

    //This method will be called when your application has loaded and is receiving a url,
    //regardless of whether it was in memory or not at the time.  I found no need to put
    //any extra logic in FinishedLaunching.
    public override bool HandleOpenUrl(NSUrl url)
    {
        //The URL your application receives could be of many forms
        //We'll check here to make sure that it's not null, and that 
        //it refers to a file on disk - that is, it starts with file://
        if (url != null && url.IsFileUrl)
        {
            //Pass the URL to the root view controller for handling
            viewController.HandleOpenUrl(url);
        }
    }
}

Handling the bulk of the import and export will be done in your view controller. Here's what your view controller might look like:

using System.IO;
using MonoTouch.Foundation;
using MonoTouch.MessageUI;

...

public class MyViewController : UIViewController
{
    ...

    //We need a field to prevent the composer from being garbage collected 
    //after ShowMailComposer() goes out of scope.  Otherwise, you'll wind
    //up with a NullReferenceException.
    MFMailComposeViewController sendMail;

    public void ExportFileViaEmail(string fileToExport)
    {
        //Check to make sure we can send mail
        if (MFMailComposeViewController.CanSendMail)
        {
           //Create the mail composer
           sendMail = new MFMailComposeViewController();

           //You can set the recipients in to, cc, and bcc fields
           sendMail.SetToRecipients(new string[] { "you@domain.com" });
           sendMail.SetCcRecipients(new string[] { "me@domain.com" });
           sendMail.SetBccRecipients(new string[] { "other@domain.com" });
           
           //You can set the message subject
           sendMail.SetSubject(string.Format("Sharing '{0}' with you", Path.GetFileName(fileToExport)));

           //You can set the message body
           //The body can be HTML if the second argument is true
           sendMail.SetMessageBody("Here is the file you wanted!", false);

           //Add data for your attachment
           //The MIME type argument needs to match what you have in your info.plist
           sendMail.AddAttachmentData(
               NSData.FromStream(new FileStream(fileToExport, FileMode.Open)), 
               "application/vnd.yourcompany.yourtype",
               Path.GetFileName(fileToExport)
           );

           //The Finished event will fire when the mail is either sent or discared.
           //Generally, you'll just dismiss the modal dialog here, but if you need
           //to do more, the event args will tell you exactly what happened.
           sendMail.Finished += (s, e) => {
               e.Controller.DismissModalViewControllerAnimated(true);
           };

           this.PresentModalViewController(sendMail, true);
        }
        else
        {
            //Do something about the fact that you can't send mail
        }
    }

    //We'll call this method from the app delegate with the url we are given
    public void ImportFile(NSUrl fileToImport)
    {
        //The most important thing to do here is copy the file from /Documents/Inbox
        //to /Documents.  You may need to assure the file name is unique, etc.
        string destPath = Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.Personal),
            Path.GetFileName(fileToImport.Path));
        File.Copy(fileToImport.Path, destPath);

        //You will probably want to do something in the UI to open the file here

        //It's also a good idea to remove it from the inbox when you're done
        File.Delete(fileToImport);
    }
}

No comments:

Post a Comment