У меня есть НАСТОЯЩЕЕ недоразумение с MFMailComposeViewController в Swift (iOS8) в симуляторе

Я создаю CSV-файл и пытаюсь отправить его по электронной почте. Отображает окно для отправки почты, но не заполнено телом письма и без вложенного файла. Приложение зависает с этим экраном:

prntscr.com/4ikwwm

Кнопка "Отмена" не работает. Через несколько секунд в консоли появится:

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

Вот мой код:

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

Попытка вместо того, чтобы отправить почту, используя UIActivityViewController:

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

Смотрите примерно тот же экран для отправки электронной почты, который через некоторое время возвращается к предыдущему экрану. В консоли теперь другая ошибка:

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

Было что-то PlugInKit,

Попытка вместо UIActivityViewController с помощью UIDocumentInteractionController:

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

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

Я вижу этот экран с содержимым CSV-файла: http://prntscr.com/4ilgax Я нажимаю кнопку экспорта в правом верхнем углу и вижу этот экран http://prntscr.com/4ilguk где я выбираю MAIL и в течение нескольких секунд Я вижу http://prntscr.com/4ilh2h Затем возвращается к отображению содержимого файла! В консоли те же сообщения, что и при использовании UIActivityViewController,

7 ответов

Решение

* * ВАЖНО - НЕ ИСПОЛЬЗУЙТЕ ИМИТАТОР ДЛЯ ЭТОГО. * *

Даже в 2016 году симуляторы очень просто не поддерживают отправку почты из приложений.

Действительно, на симуляторах просто нет почтовых клиентов.

Но! Посмотрите сообщение внизу!


Анри дал полный ответ. Вы должны

- выделить и инициировать MFMailComposeViewController на более ранней стадии, и

- держать его в одной статической переменной, а затем,

- когда это необходимо, получите статический экземпляр MFMailComposeViewController и используйте его.

И вам почти наверняка придется цикл глобального MFMailComposeViewController после каждого использования. Это не надежно, чтобы повторно использовать тот же.

Имейте глобальную рутину, которая выпускает и затем повторно инициализирует синглтон MFMailComposeViewController, Призывайте к этой глобальной процедуре каждый раз, когда вы закончите работу с составителем почты.

Делай это в любом синглтоне. Не забывайте, что ваш делегат приложения, конечно, одноэлементный, так что сделайте это там...

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

а также...

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

Затем использовать почту, что-то вроде этого...

-(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, исправлена ​​опечатка в соответствии с Майклом Саламоном ниже.}

Для удобства используйте следующий макрос в вашем файле префикса

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

Также вот "незначительная" проблема, которая может стоить вам дней: /questions/46313160/znachenie-preduprezhdeniya-vo-vremya-prezentatsii/46313231#46313231


Только на 2016 FTR вот основной быстрый код для отправки электронного письма в приложении,

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

Тем не мение! Заметка!

В наши дни отправка электронного письма "в приложении" - это дерьмо.

Сегодня гораздо проще просто отключиться от почтового клиента.

Добавить в листинг...

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

а затем код как

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

В наши дни это гораздо лучше, чем пытаться писать изнутри приложения по электронной почте.

Помните еще раз, что симуляторы iOS просто не имеют почтовых клиентов (и при этом вы не можете отправлять электронную почту, используя композитор в приложении). Вы должны проверить на устройстве.

Это не имеет ничего общего со Свифтом. Это проблема с почтовым композитором, который существует всегда, кажется. Эта вещь чрезвычайно требовательна: от сбоя по тайм-ауту до отправки сообщений делегата, даже если они отменены.

Обходной путь, который все используют, заключается в создании глобального почтового компоновщика (например, в синглтоне) и его повторной инициализации каждый раз, когда вам это нужно. Это гарантирует, что почтовый компоновщик всегда рядом, когда это требуется ОС, но также и то, что он свободен от любого дерьма, когда вы хотите использовать его повторно.

Поэтому создайте сильную (как можно более глобальную) переменную, содержащую почтовый композитор, и сбрасывайте его каждый раз, когда вы захотите его использовать.

  • XCode 6 Simulator имеет проблемы с управлением Mailcomposer и другими вещами.
  • Попробуйте протестировать код на реальном устройстве. Скорее всего, это будет работать.
  • У меня проблемы при запуске MailComposer с кнопки actionSheet, в том числе с реальным тестом. С IOS 7 работал нормально, тот же код в IOS 8 не работает. Для меня Apple должна испортить XCode 6. (слишком много разных смоделированных устройств с Objective-C и Swift вместе...)

Создайте свойство для почтового компоновщика и создайте его экземпляр, если он действительно загружен, чем вызываете его, когда вам нужен почтовый композитор.

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

Эй, это решено с iOS 8.3, выпущенной 2 дня назад.

Не уверен, необходима ли переработка, предложенная в вышеуказанном решении, или нет. Но вам нужно использовать правильные параметры.

Делегат получает MFMailComposeViewController* parameter, И вам нужно использовать это вместо self при увольнении контроллера. Т.е.

Делегат получает (MFMailComposeViewController *) controller, И вам нужно использовать это вместо self при отклонении MFMailComposeViewController controller, Это то, что вы хотите уволить в конце концов.

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

Простой вспомогательный класс для обработки почты в Swift. Основано на ответе Джо Блоу.

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

Поместите переменную экземпляра в класс AppDelegate и вызывайте при необходимости.

Другие вопросы по тегам