56 votes

J'ai un VRAI malentendu avec MFMailComposeViewController en Swift (iOS8) dans le simulateur.

Je crée un fichier CSV et j'essaie de l'envoyer par e-mail. Une fenêtre d'envoi de mail s'affiche, mais le corps du mail n'est pas rempli, et il n'y a pas de fichier attaché. L'application se bloque avec cet écran :

prntscr.com/4ikwwm

Le bouton "Annuler" ne fonctionne pas. Après quelques secondes, 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

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

J'essaie plutôt d'envoyer du 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 e-mail, qui après un certain temps de 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 PlugInKit .

Essayer plutôt UIActivityViewController en utilisant UIDocumentInteractionController :

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

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

Je vois cet écran avec le contenu d'un fichier CSV :

enter image description here

J'appuie sur le bouton d'exportation en haut à droite et je vois cet écran :

enter image description here

où je choisis MAIL et pendant plusieurs secondes je vois :

enter image description here

Puis revient à l'affichage du contenu du fichier ! Dans la console, les mêmes messages que lors de l'utilisation de UIActivityViewController .

118voto

Joe Blow Points 3618

* * 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 depuis des applications.

En effet, les simulateurs ne disposent tout simplement pas de clients de messagerie.

Mais voyez le message en bas de page !


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

-- allouer et initier MFMailComposeViewController dans une étape antérieure et

-- le conserver dans une variable statique et ensuite,

-- Lorsque vous en avez besoin, récupérez l'instance statique de MFMailComposeViewController et utilisez-la.

ET il est presque certain que vous devrez faire tourner le contrôleur global MFMailComposeViewController après chaque utilisation. Il est no fiable pour réutiliser le même.

Avoir une routine globale qui libère et réinitialise le singleton. MFMailComposeViewController . Faites appel à cette routine globale, chaque fois, une fois que vous avez terminé avec le compositeur de courrier.

Faites-le dans n'importe quel singleton. N'oubliez pas que votre délégué d'application est, bien sûr, un singleton, alors faites-le là...

@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];
    }

Ensuite, 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, correction d'une coquille par Michael Salamone ci-dessous.}

Pour plus de commodité, ayez la macro suivante dans votre fichier Prefix

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

Voici également 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 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)
        }

Cependant ! Note !

De nos jours, l'envoi d'un courrier électronique "dans l'application" est une véritable catastrophe.

Il est beaucoup plus facile aujourd'hui de se contenter d'un client de messagerie.

Ajouter à la liste ...

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

et ensuite du 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")
        }
    }

De nos jours, c'est beaucoup mieux que d'essayer d'envoyer un courriel depuis l'intérieur de l'application.

N'oubliez pas que les simulateurs iOS ne disposent tout simplement pas de clients de messagerie (et que vous ne pouvez pas non plus envoyer d'e-mails à l'aide du compositeur dans une application). Vous devez tester sur un appareil.

1 votes

Merci de développer ma réponse :) . Il est également important de souligner qu'il faut faire tourner le compositeur de courrier JUSTE AVANT de le réutiliser. Cela évite bien des maux de tête dus à des plantages dus à des méthodes déléguées. L'idée est de toujours garder le mail composer joyeusement vivant jusqu'à ce que vous ayez besoin de le réutiliser. À ce moment-là, vous le recyclez.

0 votes

... Quoi qu'il en soit, je suis très intéressé d'entendre votre rapport selon lequel vous avez trouvé qu'il était préférable de faire du vélo juste AVANT de l'utiliser. Comme je l'ai dit, bizarrement, nous avons trouvé qu'il était préférable de pédaler après ! (Quel gâchis de la part d'Apple, juste pour envoyer un email !) BTW avez-vous remarqué les différents rapports des gens que les "polices personnalisées" pourraient affecter le problème. Je n'ai jamais trouvé cette information. Vous êtes génial, cordialement

1 votes

J'ai découvert que la seule chose qui fonctionnait pour moi était de m'assurer que le dernier compositeur de courrier utilisé était disponible en permanence, jusqu'à ce que j'en aie à nouveau besoin. À ce moment-là, je recycle et je fais mon travail. Mais l'ensemble est tellement bogué que je ne suis pas surpris que vous ayez une façon différente de procéder. Oh, et j'utilise aussi des polices personnalisées.

17voto

Henri Asseily Points 1801

Cela n'a rien à voir avec Swift. C'est un problème avec le compositeur de courrier qui existe depuis toujours, semble-t-il. Cette chose est extrêmement pointilleuse, allant de l'échec avec des délais d'attente à l'envoi de messages de délégués même s'ils sont annulés.

La solution de rechange que tout le monde utilise consiste à créer un compositeur de courrier global (par exemple dans un singleton), et à le réinitialiser à chaque fois que vous en avez besoin. Cela permet de s'assurer que le compositeur de courrier est toujours présent lorsque le système d'exploitation en a besoin, mais aussi qu'il est libre de toute saleté lorsque vous voulez le réutiliser.

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

3 votes

J'ai le même problème - uniquement sur iOS8. Et mon code est ObjC, donc ce n'est pas Swift qui est à blâmer.

2 votes

Hm, je viens de l'essayer sur le matériel et ça marche bien. Il semble que ce soit un problème uniquement dans le simulateur iOS8.

0 votes

@AXE, attention, le problème est toujours là en 7 voire 8. Notez qu'il est seulement "erratique" (dépend d'autres choses) donc vous ne le verrez pas à chaque fois. Nous devons tous utiliser MFMailComposeViewController dans un statique, c'est tout ce qu'il y a à faire (et vous devez le cycler). Ils devraient simplement mettre cela dans la doco. Heureusement, ce n'est pas difficile à faire :-O

6voto

Murolau Murolau Points 41
  • Le simulateur XCode 6 a des problèmes pour gérer Mailcomposer et d'autres choses.
  • Essayez de tester le code avec un appareil réel. Il est probable qu'il fonctionne.
  • J'ai des problèmes lorsque je lance MailComposer à partir du bouton de la feuille d'action, également avec un test réel. Avec IOS 7 cela fonctionnait bien, le même code dans IOS 8 ne fonctionne pas. Pour moi Apple doit dépurer le XCode 6 ( trop de simulations d'appareils différents avec Objective-C et Swift ensemble ...)

1voto

Je ne sais pas si le recyclage proposé dans la solution ci-dessus est nécessaire ou non. Mais vous devez utiliser les paramètres appropriés.

Le délégué reçoit un MFMailComposeViewController* parameter . Et vous devez utiliser cela au lieu de self lors du rejet du contrôleur. C'est-à-dire

Le délégué reçoit le (MFMailComposeViewController *) controller . Et vous devez utiliser cela au lieu de self lors du rejet de la MFMailComposeViewController controller . C'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 votes

Je ne suis pas d'accord avec ça. Vous ne devriez pas rejeter en utilisant controller vous devez écarter de la CV self qui a fait la présentation [self dismissViewControllerAnimated:YES completion:nil] . C'est la norme et les documents le recommandent également. developer.apple.com/library/ios/documentation/MessageUI/

0 votes

Bonjour Michael - juste TBC merci de m'avoir signalé la faute de frappe dans mon code. Pour tous ceux qui lisent ceci maintenant, j'avais accidentellement tapé "self dismiss..." au lieu de "controller dismiss...". Naturellement, Michael a raison à 100 %. Notez que si vous lisez ceci "maintenant", je l'ai déjà modifié dans la réponse ci-dessus également. Merci encore à Michael !

0 votes

Je ne pense pas que le fait d'envoyer un message de rejet à soi-même ou au contrôleur change quoi que ce soit, comme l'indique la documentation : The presenting view controller is responsible for dismissing the view controller it presented. If you call this method on the presented view controller itself, UIKit asks the presenting view controller to handle the dismissal.

1voto

Michal Shatz Points 377

Créez une propriété pour le compositeur de courrier et instanciez-le dans la vue chargée puis appelez-le quand 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:^{}];

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X