J'ai vraiment un malentendu avec MFMailComposeViewController dans Swift (iOS8) dans Simulateur

je crée un fichier csv et j'essaie de l'envoyer par e-mail. Affiche une fenêtre pour envoyer du courrier, mais n'est pas rempli avec le corps de l'email, et aucun fichier joint. Application accroche avec cet écran:

prntscr.com/4ikwwm

bouton "Cancel" ne fonctionne pas. Après quelques secondes dans la console apparaît:

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

il y a mon 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!")
        }
    }
}

Essayer à la place d'envoyer le courrier en utilisant UIActivityViewController :

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

voir à peu près le même écran pour envoyer un courriel, qui après un retour à l'écran précédent. Dans la console, maintenant une autre erreur:

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

il y avait quelque chose à propos de PlugInKit .

d'Essayer au lieu de UIActivityViewController à l'aide UIDocumentInteractionController :

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

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

je vois cet écran avec un contenu un fichier CSV: http://prntscr.com/4ilgax j'appuie sur le bouton Exporter en haut à droite et voir cet écran http://prntscr.com/4ilguk où je choisis le courrier et et pendant plusieurs secondes je vois http://prntscr.com/4ilh2h retourne alors à l'affichage du contenu du fichier! Dans la console les mêmes messages que lors de l'utilisation de UIActivityViewController .

55
demandé sur Fattie 2014-09-01 15:12:00

7 réponses

* * IMPORTANT - N'UTILISEZ PAS LE SIMULATEUR POUR CELA. * *

même en 2016, les simulateurs ne prennent tout simplement pas en charge l'envoi de courrier à partir d'applications.

en effet, les simulateurs n'ont tout simplement pas de clients mail.

mais! Faire voir le message en bas!


Henri a donné la réponse totale. Vous devez

-- attribuer et lancer MFMailComposeViewController à un stade antérieur , et

-- tenir dans une variable statique , puis,

-- quand c'est nécessaire, récupérez l'instance MFMailComposeViewController statique et utilisez-la.

et vous aurez presque certainement à faire le cycle du contrôleur Mfmailcomposeview après chaque utilisation. Il est pas fiable pour réutiliser le même.

a une routine globale qui libère puis réinitialise le singleton MFMailComposeViewController . Appel à cette routine globale, à chaque fois, après que vous avez fini avec le compositeur de courrier.

Faire quelque singleton. N'oubliez pas que votre délégué d'application est, bien sûr, un singleton, alors le faire...

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

et...

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

alors pour utiliser le courrier, quelque chose comme ça ...

-(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, typographie fixe par Michael Salamone ci-dessous.}

avoir la macro suivante dans votre fichier de préfixe pour plus de commodité

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

voici aussi un problème "mineur" qui peut vous coûter des jours: https://stackoverflow.com/a/17120065/294884


juste pour 2016 FTR voici le code swift de base pour envoyer un e-mail dans 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)
        }

Toutefois! Remarque!

de nos jours, c'est merdique d'envoyer un email "in app".

il est beaucoup mieux aujourd'hui de simplement couper au client de courrier électronique.

ajouter à plist ...

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

et puis le code comme

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

Ces jours-ci, c'est beaucoup mieux que d'essayer d'email de "l'intérieur" de l'application.

Rappelez-vous la les simulateurs iOS n'ont tout simplement pas de clients e-mail (vous ne pouvez pas non plus envoyer d'e-mail en utilisant le compositeur dans une application). Vous devez tester sur un appareil.

117
répondu Fattie 2017-05-23 11:47:01

cela n'a rien à voir avec Swift. C'est un problème avec le compositeur du courrier qui existe depuis toujours, semble-t-il. Cette chose est extrêmement difficile, de l'échec avec les temps morts à l'envoi de messages de délégué, même en cas d'annulation.

la solution que tout le monde utilise est de créer un compositeur de courrier global (par exemple dans un singleton), et à chaque fois le réinitialiser quand vous en avez besoin. Cela garantit que le compositeur du courrier est toujours là quand L'OS en a besoin, mais aussi qu'il est sans aucune Merde quand tu veux la réutiliser.

créez donc une variable forte (aussi globale que possible) contenant le compositeur du courrier et réinitialisez-la à chaque fois que vous voulez l'utiliser.

17
répondu Rikkles 2014-09-01 11:35:14
  • XCode 6 Simulateur a des problèmes de gestion de Mailcomposer et d'autres choses.
  • essayez de tester le code avec un vrai appareil. Il a de chance de travailler.
  • j'ai des problèmes lors de l'exécution MailComposer à partir du bouton actionSheet, également avec le test réel. Avec IOS 7 fonctionne bien, le même code dans IOS 8 ne fonctionne pas. Pour moi Apple doit dépurer le XCode 6. ( trop d'appareils simulés différents avec Objectif-C et Swift ensemble ...)
6
répondu Murolau Murolau 2014-09-26 09:58:09

pas sûr si le recyclage proposé dans la solution ci-dessus est nécessaire ou non. Mais vous devez utiliser des paramètres appropriés.

le délégué reçoit un MFMailComposeViewController* parameter . Et vous devez utiliser cela au lieu de self en rejetant le contrôleur. C'est-à-dire:

le délégué reçoit Le (MFMailComposeViewController *) controller . Et vous devez utiliser cela au lieu de self en rejetant le MFMailComposeViewController controller . Qu'est ce que vous voulez rejeter, après tout.

-(void)mailComposeController:(MFMailComposeViewController *)controller
     didFinishWithResult:(MFMailComposeResult)result
     error:(NSError *)error
    {
    [controller dismissViewControllerAnimated:YES completion:^
        { [APP cycleTheGlobalMailComposer]; }
        ];
    }
1
répondu Michael Salamone 2014-11-14 16:36:05

créer une propriété pour le compositeur de courrier et instanciez-le en vue ne charge que l'appeler lorsque jamais vous avez besoin d'un compositeur de courrier.

@property (strong, nonatomic) MFMailComposeViewController *mailController;
self.mailController = [[MFMailComposeViewController alloc] init];
[self presentViewController:self.mailController animated:YES completion:^{}];
1
répondu Michal Shatz 2015-01-13 12:01:59

Hey c'est résolu avec iOS 8.3 sorti il y a 2 jours.

1
répondu Chriss Mejía 2015-04-15 13:20:41

une classe d'assistant simple pour le traitement du courrier dans Swift. Basé sur la réponse de Joe Blow.

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 comme variable d'instance dans AppDelegate-class et appel si nécessaire.

1
répondu Sunkas 2015-12-11 08:35:13