Alexey Nakhimov Alexey Nakhimov - 3 months ago 10
iOS Question

I have REAL misunderstanding with MFMailComposeViewController in Swift (iOS8) in Simulator

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:

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

There is my code:

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

var csvString = NSMutableString()

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)


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);


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

var testData: NSData = NSData(contentsOfFile: plistpath)

var myMail: MFMailComposeViewController = MFMailComposeViewController()


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

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

There was something about

Trying instead

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

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

I see this screen with contents a CSV-file: I press button export in top-right and see this screen where I choose MAIL and and for several seconds I see Then returns to displaying the contents of the file! In the console the same messages as when using



Even in 2016, the simulators very simply do not support sending mail from apps. And the simulators simply do not have mail clients on them.

But! Do see the message at the bottom!

Henri has given the total answer. 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.

Do it in any singleton. Don't forget that your app delegate is, of course, a singleton, so do it there...

@property (nonatomic, strong) MFMailComposeViewController *globalMailComposer;

-(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


    // cycling GlobalMailComposer due to idiotic iOS issue
    self.globalMailComposer = nil;
    self.globalMailComposer = [[MFMailComposeViewController alloc] init];

Then to use the mail, something like this ...

    // 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];
        [UIAlertView ok:@"Unable to mail. No email on this device?"];
        [APP cycleTheGlobalMailComposer];

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

{nb, fixed typo per Michael Salamone below.}

Have the following macro in your Prefix file for convenience

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

Also here's a "minor" problem which can cost you days:

Just for 2016 FTR here's the basic swift code to send an email IN APP,

class YourClass:UIViewController, MFMailComposeViewControllerDelegate
    func clickedMetrieArrow()
        print("click arrow!  v1")
        let e = MFMailComposeViewController()
        e.mailComposeDelegate = self
        e.setToRecipients( [""] )
        e.setSubject("Blah subject")
        e.setMessageBody("Blah text", isHTML: false)
        presentViewController(e, animated: true, completion: nil)

    func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?)
        dismissViewControllerAnimated(true, completion: nil)



These days it is REALLY CRAP to send an email "in app".

Just cut away to the email client - it's far better UX, today.

Add to plist ...


and then something like

func idioticMarketingEmailForClient()
    let subject = "Some subject"
    let body = "Plenty of email body."

    let coded = "\(subject)&body=\(body)".stringByAddingPercentEncodingWithAllowedCharacters(.URLQueryAllowedCharacterSet())

    if let emailURL:NSURL = NSURL(string: coded!)
        if UIApplication.sharedApplication().canOpenURL(emailURL)
            print("fail A")
        print("fail B")

That is far better, today, than trying to email from "inside" the app.

Remember again the iOS simulators simply don't have email clients (nor can you send email with the composer from an app), test on a device.