Подтвердить что ты не робот

У меня REAL непонимание с MFMailComposeViewController в Swift (iOS8) в Simulator

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

4b9b3361

Ответ 1

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

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

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

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


Генри дал полный ответ. Вы ДОЛЖНЫ

- выделять и запускать MFMailComposeViewController на более раннем этапе и

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

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

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

У вас есть глобальная подпрограмма, которая освобождает, а затем повторно инициализирует singleton 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])

Также здесь "незначительная" проблема, которая может стоить вам дней: fooobar.com/questions/99537/...


Только для 2016 FTR здесь базовый быстрый код для отправки электронной почты 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)
        }

Однако! Внимание!

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

Намного лучше сегодня просто отключить почтовый клиент.

Добавить в plist...

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

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

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

Ответ 2

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

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

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

Ответ 3

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

Ответ 4

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

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

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

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

Ответ 5

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

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

Ответ 6

Привет, это решено с выпуском iOS 8.3, выпущенным 2 дня назад.

Ответ 7

Простой вспомогательный класс для обработки почты в 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 и вызовите при необходимости.