ios - mfmailcomposeviewcontrollerdelegate - swift 3 send email
Tengo un verdadero malentendido con MFMailComposeViewController en Swift(iOS8) en Simulator (7)
* * IMPORTANTE: NO USE EL SIMULADOR PARA ESTO. * *
Incluso en 2016, los simuladores simplemente no admiten el envío de correo desde aplicaciones.
De hecho, los simuladores simplemente no tienen clientes de correo.
¡Pero! ¡Vea el mensaje en la parte inferior!
Henri ha dado la respuesta total. Debes
- asignar e iniciar MFMailComposeViewController en una etapa anterior , y
- mantenlo en una variable estática , y luego,
- cuando sea necesario, obtenga la instancia estática de MFMailComposeViewController y úsela.
Y seguramente tendrá que cambiar el MFMailComposeViewController global después de cada uso. No es confiable reutilizar el mismo.
Tenga una rutina global que libere y luego reinicialice el
MFMailComposeViewController
singleton.
Llame a esa rutina global, cada vez, una vez que haya terminado con el compositor de correo.
Hazlo en cualquier singleton. No olvides que el delegado de tu aplicación es, por supuesto, un singleton, así que hazlo allí ...
@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
.........
}
y...
-(void)cycleTheGlobalMailComposer
{
// cycling GlobalMailComposer due to idiotic iOS issue
self.globalMailComposer = nil;
self.globalMailComposer = [[MFMailComposeViewController alloc] init];
}
Luego, para usar el correo, algo como esto ...
-(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, error tipográfico corregido por Michael Salamone a continuación.}
Tenga la siguiente macro en su archivo de prefijo para mayor comodidad
#define APP ((AppDelegate *)[[UIApplication sharedApplication] delegate])
También aquí hay un problema "menor" que puede costarle días: https://stackoverflow.com/a/17120065/294884
Solo para 2016 FTR, aquí está el código básico rápido para enviar un correo electrónico EN LA APLICACIÓN,
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)
}
¡Sin embargo! ¡Nota!
En estos días es una mierda enviar un correo electrónico "en la aplicación".
Hoy es mucho mejor simplemente cortar al cliente de correo electrónico.
Añadir a plist ...
<key>LSApplicationQueriesSchemes</key>
<array>
<string>instagram</string>
</array>
y luego codificar como
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")
}
}
En estos días, eso es mucho mejor que intentar enviar correos electrónicos desde "dentro" de la aplicación.
Recuerde nuevamente que los simuladores de iOS simplemente no tienen clientes de correo electrónico (ni puede enviar correos electrónicos utilizando el compositor dentro de una aplicación). Debes probar en un dispositivo.
Creo un archivo CSV e intento enviarlo por correo electrónico. Muestra una ventana para enviar correo, pero no está lleno con el cuerpo del correo electrónico y no tiene un archivo adjunto. La aplicación se cuelga con esta pantalla:
el botón "Cancelar" no funciona. Después de unos segundos en la consola aparece:
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
Ahí está mi código:
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!")
}
}
}
Intentando, en cambio, enviar correo usando
UIActivityViewController
:
let fileURL: NSURL = NSURL(fileURLWithPath: plistpath)
let actViewController = UIActivityViewController(activityItems: [fileURL], applicationActivities: nil)
self.presentViewController(actViewController, animated: true, completion: nil)
Vea aproximadamente la misma pantalla para enviar correos electrónicos, que luego de un tiempo regresa a la pantalla anterior. En la consola, ahora otro error:
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
Había algo sobre
PlugInKit
.
Intentando en
UIActivityViewController
lugar
UIActivityViewController
usando
UIDocumentInteractionController
:
let docController = UIDocumentInteractionController(URL: fileURL)
docController.delegate = self
docController.presentPreviewAnimated(true)
...
func documentInteractionControllerViewControllerForPreview(controller: UIDocumentInteractionController!) -> UIViewController! {
return self
}
Veo esta pantalla con contenido de un archivo CSV:
Presiono el botón exportar en la esquina superior derecha y veo esta pantalla:
donde elijo CORREO y por varios segundos veo:
Luego vuelve a mostrar el contenido del archivo!
En la consola, los mismos mensajes que cuando se usa
UIActivityViewController
.
Crear una propiedad para el redactor de correo e instanciarla a la vista se cargó que llamarla cuando necesite un compositor de correo.
@property (strong, nonatomic) MFMailComposeViewController *mailController;
self.mailController = [[MFMailComposeViewController alloc] init];
[self presentViewController:self.mailController animated:YES completion:^{}];
Hola, esto se resuelve con iOS 8.3 lanzado hace 2 días.
No estoy seguro si el reciclaje propuesto en la solución anterior es necesario o no. Pero sí necesita usar los parámetros adecuados.
El delegado recibe un
MFMailComposeViewController* parameter
.
Y debe usar eso en lugar de
self
al descartar el controlador.
Es decir
El delegado recibe el
(MFMailComposeViewController *) controller
.
Y debe usar eso en lugar de
self
al descartar el
MFMailComposeViewController controller
.
Eso es lo que quieres descartar después de todo.
-(void)mailComposeController:(MFMailComposeViewController *)controller
didFinishWithResult:(MFMailComposeResult)result
error:(NSError *)error
{
[controller dismissViewControllerAnimated:YES completion:^
{ [APP cycleTheGlobalMailComposer]; }
];
}
No tiene nada que ver con Swift. Parece un problema con el compositor de correo que ha existido desde siempre. Esa cosa es extremadamente exigente, desde fallar con los tiempos de espera hasta enviar mensajes de delegado incluso cuando se cancela.
La solución alternativa que todos usan es crear un compositor de correo global (por ejemplo, en un singleton), y reiniciarlo cada vez que lo necesite. Esto garantiza que el compositor de correo siempre esté disponible cuando el sistema operativo lo necesite, pero también que esté libre de basura cuando desee reutilizarlo.
Así que cree una variable fuerte (lo más global posible) que contenga el compositor de correo y reinícielo cada vez que quiera usarlo.
Una clase de ayuda simple para manejar el correo en Swift. Basado en la respuesta 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()
}
}
}
Coloque como variable de instancia en AppDelegate-class y llame cuando sea necesario.
- XCode 6 Simulator tiene problemas para administrar Mailcomposer y otras cosas.
- Intenta probar el código con un dispositivo real. Probablemente funcionará.
- Tengo problemas al ejecutar MailComposer desde el botón actionSheet, también con prueba real. Con IOS 7 funcionó bien, el mismo código en IOS 8 no funciona. Para mí, Apple debe depurar el XCode 6. (demasiados dispositivos simulados diferentes con Objective-C y Swift juntos ...)