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

六月ゝ 毕业季﹏ 提交于 2019-11-26 11:39:00
Fattie

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

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

Indeed, the simulators simply do not have mail clients.

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 to 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
    .........
    }

and...

-(void)cycleTheGlobalMailComposer
    {
    // cycling GlobalMailComposer due to idiotic 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, fixed typo per Michael Salamone below.}

Have the following macro in your Prefix file for convenience

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

Also here's a "minor" problem which can cost you days: https://stackoverflow.com/a/17120065/294884


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( ["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! Note!

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

It's much better today to simply cut away to the email client.

Add to plist ...

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

and then code like

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, that is much better than trying to email from "inside" the app.

Remember again the iOS simulators simply do not have email clients (nor can you send email using the composer within an app). You must test on a device.

It has nothing to do with Swift. It's an issue with the mail composer that's been around forever it seems. That thing is extremely picky, from failing with timeouts to sending delegate messages even when cancelled.

The workaround everyone uses is to create a global mail composer (for example in a singleton), and every single time reinitializing it when you need it. This ensures the mail composer is always around when the OS needs it, but also that it is free of any crap when you want to reuse it.

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

  • XCode 6 Simulator has problems managing Mailcomposer and other things.
  • Try testing the code with a real device. Likely it will work.
  • I have problems when running MailComposer from actionSheet button, also with real test. With IOS 7 worked fine, the same code in IOS 8 does not work. For me Apple must depurated the XCode 6. ( too many different simulated devices with Objective-C and Swift together ...)
Michael Salamone

Not sure if the recycling proposed in above solution is necessary or not. But you do need use proper parameters.

The delegate receives a MFMailComposeViewController* parameter. And you need to use that instead of self when dismissing the controller. I.e.

The delegate receives the (MFMailComposeViewController *) controller. And you need to use that instead of self when dismissing the MFMailComposeViewController controller. That is what you want to dismiss after all.

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

Create a property for the mail composer and instantiate it in view did load than call it when ever you need a mail composer.

@property (strong, nonatomic) MFMailComposeViewController *mailController;
self.mailController = [[MFMailComposeViewController alloc] init];
[self presentViewController:self.mailController animated:YES completion:^{}];

Hey this is solved with iOS 8.3 released 2 days ago.

A simple helper class for handling mail in Swift. Based on Joe Blow'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 as instance variable in AppDelegate-class and call when needed.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!