Je crée un fichier CSV et essaie de l'envoyer par courrier électronique. Affiche une fenêtre pour envoyer un courrier, mais ne contient pas le corps du courrier électronique et aucun fichier joint. L'application se bloque avec cet écran:
le bouton "Annuler" 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
Voici 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!")
}
}
}
Essayez à la place d'envoyer un 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 courrier électronique qui, après un certain temps, revient à 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
.
Essayer à la place 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:
J'appuie sur le bouton export en haut à droite et vois cet écran:
où je choisis MAIL et pendant quelques secondes je vois:
Puis revient à afficher le contenu du fichier! Dans la console, les mêmes messages que lorsque vous utilisez UIActivityViewController
.
Même en 2016, les simulateurs ne prennent tout simplement pas en charge l'envoi de courrier à partir d'applications.
En effet, les simulateurs ne disposent tout simplement pas de clients de messagerie.
Henri a donné la réponse totale. Vous devez
- allouer et lancer MFMailComposeViewController à un stade antérieur , et
- maintenez-le dans une variable statique , puis,
- Chaque fois que cela est nécessaire, obtenez l'instance statique MFMailComposeViewController et utilisez-la.
ET vous devrez presque certainement faire un cycle du MFMailComposeViewController global après chaque utilisation. Il est pas fiable de réutiliser le même.
Ayez une routine globale qui libère puis réinitialise le singleton MFMailComposeViewController
. Appelez cette routine globale, à chaque fois, une fois que vous avez terminé avec le composeur de courrier.
Faites-le dans n'importe quel singleton. N'oubliez pas que votre délégué aux applications est bien sûr un singleton, alors faites-le là-bas ...
@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 faute de frappe 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 également un problème "mineur" qui peut vous coûter des jours: https://stackoverflow.com/a/17120065/294884
Juste pour 2016 FTR, voici la base Swift code pour envoyer un email IN APP,
class YourClass:UIViewController, MFMailComposeViewControllerDelegate
{
func clickedMetrieArrow()
{
print("click arrow! v1")
let e = MFMailComposeViewController()
e.mailComposeDelegate = self
e.setToRecipients( ["[email protected]"] )
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)
}
Ajouter à la pliste ...
<key>LSApplicationQueriesSchemes</key>
<array>
<string>instagram</string>
</array>
et puis code comme
func pointlessMarketingEmailForClient()
{
let subject = "Some subject"
let body = "Plenty of <i>email</i> body."
let coded = "mailto:[email protected]?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'envoyer un courriel de "l'intérieur" de l'application.
Rappelez-vous que les simulateurs iOS n'ont tout simplement pas de clients de messagerie (vous ne pouvez pas non plus envoyer de courrier électronique à l'aide de composer dans une application). Vous devez tester sur un appareil.
Cela n'a rien à voir avec Swift. C'est un problème avec le mail composer) qui existe depuis toujours, semble-t-il. Cette chose est extrêmement difficile, de l'échec avec les délais d'attente à l'envoi de messages de délégué même en cas d'annulation.
La solution de contournement que tout le monde utilise consiste à créer un courrier global composer (par exemple dans un singleton)), et à le réinitialiser à chaque fois que vous en avez besoin. Le courrier est ainsi composer est toujours là quand le système d’exploitation en a besoin, mais aussi qu’il est exempt de toute mauvaise merde lorsque vous souhaitez le réutiliser.
Créez donc une variable forte (aussi globale que possible) contenant le courrier composer) et réinitialisez-la à chaque fois que vous souhaitez l’utiliser.
Créez une propriété pour le courrier composer) et instanciez-le en vue. Il s'est chargé puis appelé à chaque fois que vous avez besoin d'un composeur de courrier.
@property (strong, nonatomic) MFMailComposeViewController *mailController;
self.mailController = [[MFMailComposeViewController alloc] init];
[self presentViewController:self.mailController animated:YES completion:^{}];
Pas sûr que le recyclage proposé dans la solution ci-dessus soit 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
lorsque vous quittez le contrôleur. C'est à dire.
Le délégué reçoit le (MFMailComposeViewController *) controller
. Et vous devez utiliser cela au lieu de self
lorsque vous limitez le 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]; }
];
}
Une classe d'assistance simple pour gérer le 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()
}
}
}
Placez comme variable d'instance dans AppDelegate-class et appelez-le si nécessaire.
Hé, cela est résolu avec iOS 8.3 publié il y a 2 jours.