iOS 8 bug with MFMailComposeViewController

Issue :

I create a csv file and try to send it by e-mail. Displays a window to send mail, but is not filled with body of the email, and no attached file. Application hangs with this screen:

prntscr.com/4ikwwm

button “Cancel” does not work. After a few seconds in the console appears:

viewServiceDidTerminateWithError: Error Domain=_UIViewServiceInterfaceErrorDomain Code=3 "The operation couldn’t be completed. (_UIViewServiceInterfaceErrorDomain error 3.)" UserInfo=0x7f8409f29b50 {Message=Service Connection Interrupted}

<MFMailComposeRemoteViewController: 0x7f8409c89470> timed out waiting for fence barrier from com.apple.MailCompositionService

There is my code:

func actionSheet(actionSheet: UIActionSheet!, clickedButtonAtIndex buttonIndex: Int) {
    if buttonIndex == 0 {
        println("Export!")

        var csvString = NSMutableString()
        csvString.appendString("Date;Time;Systolic;Diastolic;Pulse")

        for tempValue in results {     //result define outside this function

            var tempDateTime = NSDate()
            tempDateTime = tempValue.datePress
            var dateFormatter = NSDateFormatter()
            dateFormatter.dateFormat = "dd-MM-yyyy"
            var tempDate = dateFormatter.stringFromDate(tempDateTime)
            dateFormatter.dateFormat = "HH:mm:ss"
            var tempTime = dateFormatter.stringFromDate(tempDateTime)

            csvString.appendString("\n\(tempDate);\(tempTime);\(tempValue.sisPress);\(tempValue.diaPress);\(tempValue.hbPress)")
        }

        let fileManager = (NSFileManager.defaultManager())
        let directorys : [String]? = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory,NSSearchPathDomainMask.AllDomainsMask, true) as? [String]

        if ((directorys) != nil) {

            let directories:[String] = directorys!;
            let dictionary = directories[0];
            let plistfile = "bpmonitor.csv"
            let plistpath = dictionary.stringByAppendingPathComponent(plistfile);

            println("\(plistpath)")

            csvString.writeToFile(plistpath, atomically: true, encoding: NSUTF8StringEncoding, error: nil)

            var testData: NSData = NSData(contentsOfFile: plistpath)

            var myMail: MFMailComposeViewController = MFMailComposeViewController()

            if(MFMailComposeViewController.canSendMail()){

                myMail = MFMailComposeViewController()
                myMail.mailComposeDelegate = self

                // set the subject
                myMail.setSubject("My report")

                //Add some text to the message body
                var sentfrom = "Mail sent from BPMonitor"
                myMail.setMessageBody(sentfrom, isHTML: true)

                myMail.addAttachmentData(testData, mimeType: "text/csv", fileName: "bpmonitor.csv")

                //Display the view controller
                self.presentViewController(myMail, animated: true, completion: nil)
            }
            else {
                var alert = UIAlertController(title: "Alert", message: "Your device cannot send emails", preferredStyle: UIAlertControllerStyle.Alert)
                alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
                self.presentViewController(alert, animated: true, completion: nil)


            }
        }
        else {
            println("File system error!")
        }
    }
}

Trying instead to send mail using UIActivityViewController:

let fileURL: NSURL = NSURL(fileURLWithPath: plistpath)
let actViewController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil)
self.presentViewController(actViewController, animated: true, completion: nil)

See approximately the same screen to send e-mail, which after a while returning to the previous screen. In the console, now another error:

viewServiceDidTerminateWithError: Error Domain=_UIViewServiceInterfaceErrorDomain Code=3 "The operation couldn’t be completed. (_UIViewServiceInterfaceErrorDomain error 3.)" UserInfo=0x7faab3296ad0 {Message=Service Connection Interrupted}
Errors encountered while discovering extensions: Error Domain=PlugInKit Code=13 "query cancelled" UserInfo=0x7faab3005890 {NSLocalizedDescription=query cancelled}
<MFMailComposeRemoteViewController: 0x7faab3147dc0> timed out waiting for fence barrier from com.apple.MailCompositionService

There was something about PlugInKit.

Trying instead UIActivityViewController using UIDocumentInteractionController:

let docController = UIDocumentInteractionController(URL: fileURL)
docController.delegate = self
docController.presentPreviewAnimated(true)
...

func documentInteractionControllerViewControllerForPreview(controller: UIDocumentInteractionController!) -> UIViewController! {
    return self
}

I see this screen with contents a CSV-file: http://prntscr.com/4ilgax I press button export in top-right and see this screen http://prntscr.com/4ilguk where I choose MAIL and and for several seconds I see http://prntscr.com/4ilh2h Then returns to displaying the contents of the file! In the console the same messages as when using UIActivityViewController.

SOLUTION :

* * IMPORTANT – DO NOT USE THE SIMULATOR FOR THIS. * *

Before anything else,

note that the simulator is totally hopeless for this.

This does indeed still seem to apply even now, early 2015.

Unfortunately, just forget about using the simulator. Do not even bother trying. It will just confuse matters.


Henri has given you the total answer – that’s exactly it.

You MUST

allocate and initiate MFMailComposeViewController in an earlier stage, and

hold it in one static variable, and then,

— whenever it’s needed, get the static MFMailComposeViewController instance and use that.

AND you will almost certainly have to “cycle” the global MFMailComposeViewController after each use.

It IS NOT reliable to “re-use” the same one. Have a global routine which releases and then re-initializes the singleton MFMailComposeViewController. Call that global routine, each time after you are finished with the mail composer.

So!

You can do the following in any singleton. So, a good place to do it is simply AppDelegate.h. (Don’t forget that your app delegate is, of course, a singleton. It’s a sensible place to add anything like this.)

@property (nonatomic, strong) MFMailComposeViewController *globalMailComposer;

Then, when that singleton launches, for example if using AppDelegate…

-(BOOL)application:(UIApplication *)application
   didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
    ........
    // part 3, our own setup
    [self cycleTheGlobalMailComposer];
    // needed due to the worst programming in the history of Apple Corporation
    .........
    }

and…

-(void)cycleTheGlobalMailComposer
    {
    // we are cycling the damned GlobalMailComposer... due to horrible iOS issue
    self.globalMailComposer = nil;
    self.globalMailComposer = [[MFMailComposeViewController alloc] init];
    }

Then to use the mail, something like this …

-(void)helpEmail
    {
    // APP.globalMailComposer IS READY TO USE from app launch.
    // recycle it AFTER OUR USE.

    if ( [MFMailComposeViewController canSendMail] )
        {
        [APP.globalMailComposer setToRecipients:
              [NSArray arrayWithObjects: emailAddressNSString, nil] ];
        [APP.globalMailComposer setSubject:subject];
        [APP.globalMailComposer setMessageBody:msg isHTML:NO];
        APP.globalMailComposer.mailComposeDelegate = self;
        [self presentViewController:APP.globalMailComposer
             animated:YES completion:nil];
        }
    else
        {
        [UIAlertView ok:@"Unable to mail. No email on this device?"];
        [APP cycleTheGlobalMailComposer];
        }
    }

-(void)mailComposeController:(MFMailComposeViewController *)controller
     didFinishWithResult:(MFMailComposeResult)result
     error:(NSError *)error
    {
    [controller dismissViewControllerAnimated:YES completion:^
        { [APP cycleTheGlobalMailComposer]; }
        ];
    }

{nb, per Michael Salamone below. At dismissViewControllerAnimated … you dismiss the ‘controller’ passed in to that routine, do NOT dismiss self.}

Of course have the following in your Prefix file

#define APP ((AppDelegate *)[[UIApplication sharedApplication] delegate])
// (it's worth noting that is the app delegate, not the "the application")

Hope it helps. Will save you days.

Again note that Swift is irrelevant.

Also here’s a “minor” problem (which can cost you days), note this critical tip:http://stackoverflow.com/a/17120065/294884

Senior Software Engineer
TopOfStack Software Ltd.