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

I am creating a csv file and trying to send it by email. Displays a window for sending mail, but is not filled with the body of the email and the attached file. The application freezes with this screen:

prntscr.com/4ikwwm

Button

"Cancel" does not work. After a few seconds, the console will display:

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!") } } } 

Attempting to send mail using the UIActivityViewController :

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

See roughly the same screen for sending emails, which after a while returns to the previous screen. Another error now appears in the console:

 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 PlugInKit .

Trying to use UIDocumentInteractionController instead of UIDocumentInteractionController :

 let docController = UIDocumentInteractionController(URL: fileURL) docController.delegate = self docController.presentPreviewAnimated(true) ... func documentInteractionControllerViewControllerForPreview(controller: UIDocumentInteractionController!) -> UIViewController! { return self } 

I see this screen with the contents of the CSV file: http://prntscr.com/4ilgax I click the export button in the upper right corner and see this screen http://prntscr.com/4ilguk , where I select MAIL, and for several seconds I see http://prntscr.com/4ilh2h then returns to displaying the contents of the file! The console UIActivityViewController the same messages as when using the UIActivityViewController .

+55
ios uiactivityviewcontroller uidocumentinteraction mfmailcomposer
Sep 01 '14 at 11:12
source share
7 answers

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

Even in 2016, simulators very simply do not support sending mail from applications.

Indeed, simulators simply do not have email clients.

But! Check out the message below!




Henry gave the full answer. You should

- select and run MFMailComposeViewController at an earlier stage and

- hold it in one static variable and then

- whenever necessary, get a static instance of MFMailComposeViewController and use it.

And you will almost certainly have to cycle through the global MFMailComposeViewController after each use. You cannot reuse the same one.

You have a global routine that frees and then reinitializes a singleton MFMailComposeViewController . Call this global procedure every time you are done with the mail composer.

Do it in any singleton. Do not forget that your application delegate is, of course, 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 ......... } 

and...

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

Then, to use 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, fixed typo on Michael Salamon below.}

You have the following macro in your prefix file

 #define APP ((AppDelegate *)[[UIApplication sharedApplication] delegate]) 

There is also a β€œminor” problem that may cost you days: https://stackoverflow.com/a/416829/




For 2016 FTR only, here is the basic quick code to send IN APP email,

 class YourClass:UIViewController, MFMailComposeViewControllerDelegate { func clickedMetrieArrow() { print("click arrow! v1") let e = MFMailComposeViewController() e.mailComposeDelegate = self e.setToRecipients( ["help@smhk.com"] ) 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) } 



However! Attention!

These crappy days send an "in-app" email.

It’s much better today just to disable the mail client.

Add to plist ...

 <key>LSApplicationQueriesSchemes</key> <array> <string>instagram</string> </array> 

and then enter the code as

 func pointlessMarketingEmailForClient() { let subject = "Some subject" let body = "Plenty of <i>email</i> body." let coded = "mailto:blah@blah.com?subject=\(subject)&body=\(body)".stringByAddingPercentEncodingWithAllowedCharacters(.URLQueryAllowedCharacterSet()) if let emailURL:NSURL = NSURL(string: coded!) { if UIApplication.sharedApplication().canOpenURL(emailURL) { UIApplication.sharedApplication().openURL(emailURL) } else { print("fail A") } } else { print("fail B") } } 

These days, it's much better than trying to send an email from within the app.

Remember that iOS simulators simply do not have email clients (and you cannot send emails using the composer in the application). You must test the device.

+117
Sep 16 '14 at 8:38
source share

This has nothing to do with Swift. This is a problem with the mail composer that has been around forever. This thing is extremely picky, from timeout failures to sending delegate messages, even in case of cancellation.

The workaround that everyone uses is to create a global mail composer (for example, in a singleton) and repeat its initialization each time you need it. This ensures that the mail composer is always around when he needs it, but also that he is free from all shit when you want to reuse it.

So, create a strong (as global as possible) variable containing the mail composer and reset, every time you want to use it.

+17
Sep 01 '14 at 11:35
source share
  • Xcode 6 Simulator has problems managing Mailcomposer and other things.
  • Try testing the code using a real device. It will probably work.
  • I am having problems running MailComposer from the ActionSheet button, as well as with a real test. It worked fine with iOS 7, the same code in iOS 8 doesn't work. For me, Apple must destroy Xcode 6. (Too many different simulated devices with Objective-C and Swift together ...)
+6
Sep 26 '14 at 9:58
source share

Not sure if recirculation proposed in the above solution is needed or not. But you need the right parameters.

The delegate receives the MFMailComposeViewController* parameter . And you should use this instead of self when disconnecting the controller. I.e.

The delegate receives the (MFMailComposeViewController *) controller . And you should use this instead of self when rejecting the MFMailComposeViewController controller . This is what you want to fire in the end.

 -(void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error { [controller dismissViewControllerAnimated:YES completion:^ { [APP cycleTheGlobalMailComposer]; } ]; } 
+1
Nov 14 '14 at 16:17
source share

Create a property for the mail composer and instantiate it in boot mode, rather than call it when you need the mail composer.

 @property (strong, nonatomic) MFMailComposeViewController *mailController; self.mailController = [[MFMailComposeViewController alloc] init]; [self presentViewController:self.mailController animated:YES completion:^{}]; 
+1
Dec 02 '14 at 21:17
source share

Hi, this is resolved with the release of iOS 8.3, released 2 days ago.

+1
Apr 15 '15 at 13:20
source share

A simple helper class for handling mail in Swift. Based on Joe Blau's answer.

 import UIKit import MessageUI public class EmailManager : NSObject, MFMailComposeViewControllerDelegate { var mailComposeViewController: MFMailComposeViewController? public override init() { mailComposeViewController = MFMailComposeViewController() } private func cycleMailComposer() { mailComposeViewController = nil mailComposeViewController = MFMailComposeViewController() } public func sendMailTo(emailList:[String], subject:String, body:String, fromViewController:UIViewController) { if MFMailComposeViewController.canSendMail() { mailComposeViewController!.setSubject(subject) mailComposeViewController!.setMessageBody(body, isHTML: false) mailComposeViewController!.setToRecipients(emailList) mailComposeViewController?.mailComposeDelegate = self fromViewController.presentViewController(mailComposeViewController!, animated: true, completion: nil) } else { print("Could not open email app") } } public func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) { controller.dismissViewControllerAnimated(true) { () -> Void in self.cycleMailComposer() } } } 

Place the variable as an instance in the AppDelegate class and call if necessary.

+1
Dec 09 '15 at 13:22
source share



All Articles