web-dev-qa-db-ja.com

シミュレーターのSwift(iOS8)でMFMailComposeViewControllerを誤解している

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ファイルの内容が表示されます。

enter image description here

右上のボタンエクスポートを押すと、次の画面が表示されます。

enter image description here

mAILを選択すると、数秒間表示されます。

enter image description here

その後、ファイルの内容の表示に戻ります!コンソールでUIActivityViewControllerを使用するときと同じメッセージ。

55
Alexey Nakhimov

* *重要-このためにシミュレータを使用しないでください。 * *

2016年でさえ、シミュレーターはアプリからのメール送信をサポートしていません。

実際、シミュレーターにはメールクライアントがありません。

しかし!下部にメッセージが表示されます!


アンリは完全な答えを与えました。絶対です

-早い段階でMFMailComposeViewControllerを割り当てて開始する

-1つの静的変数に保持する、その後、

-必要に応じて、静的なMFMailComposeViewControllerインスタンスを取得して使用します。

そして、ほぼ確実に、使用するたびにグローバルMFMailComposeViewControllerを循環させる必要があります。同じものを再利用することはnot信頼できません。

シングルトン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、以下のMichael Salamoneによる誤字を修正。}

便宜上、プレフィックスファイルに次のマクロを含めます

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

また、次のような「マイナーな」問題が発生し、数日かかる場合があります。 https://stackoverflow.com/a/17120065/294884


2016 FTRの場合、基本的なSwift 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シミュレーターにはメールクライアントがありません(アプリ内でcomposerを使用してメールを送信することもできません)。デバイスでテストする必要があります。

117
Fattie

Swiftとは関係ありません。それはメールの問題ですcomposerそれは永遠に存在しているようです。そのことは、タイムアウトで失敗することからキャンセルされてもデリゲートメッセージを送信することまで非常に厄介です。

誰もが使用する回避策は、グローバルメールcomposer(シングルトンなど)を作成し、必要に応じて毎回再初期化することです。これにより、メールcomposerは、OSが必要とするときに常に存在しますが、再利用したいときにもがくもありません。

したがって、メールcomposer=を保持する強力な(可能な限りグローバルな)変数を作成し、使用するたびにリセットします。

17
Rikkles
  • XCode 6シミュレータには、Mailcomposerなどの管理に問題があります。
  • 実際のデバイスでコードをテストしてみてください。おそらく動作します。
  • 実際のテストでも、actionSheetボタンからMailComposerを実行すると問題が発生します。 IOS 7は正常に動作し、IOS 8の同じコードは動作しません。私にとってはApple XCode 6.(Objective-CおよびSwift一緒に...)
6
Murolau Murolau

メールcomposerのプロパティを作成し、ビュー内でインスタンス化して、メール作成者が必要になったときに呼び出すよりもロードしました。

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

上記のソリューションで提案されているリサイクルが必要かどうかはわかりません。ただし、適切なパラメーターを使用する必要があります。

デリゲートはMFMailComposeViewController* parameterを受け取ります。また、コントローラーを閉じるときにselfの代わりにそれを使用する必要があります。つまり.

デリゲートは(MFMailComposeViewController *) controllerを受け取ります。 MFMailComposeViewController controllerを閉じるときは、selfの代わりにそれを使用する必要があります。それは結局あなたが却下したいことです。

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

Swiftでメールを処理するための単純なヘルパークラス。 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()
        }
    }
}

インスタンス変数としてAppDelegate-classに配置し、必要なときに呼び出します。

1
Sunkas

ちょっとこれは2日前にリリースされたiOS 8.3で解決されます。

1
Chriss Mejía