IT TIP

Simulator의 Swift (iOS8)에서 MFMailComposeViewController에 대한 실제 오해가 있습니다.

itqueen 2021. 1. 5. 20:45
반응형

Simulator의 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 파일 인이 화면이 표시됩니다.

여기에 이미지 설명 입력

오른쪽 상단의 내보내기 버튼을 누르면 다음 화면이 표시됩니다.

여기에 이미지 설명 입력

내가 MAIL을 선택하고 몇 초 동안 나는 다음을 본다.

여기에 이미지 설명 입력

그런 다음 파일 내용 표시로 돌아갑니다! 콘솔에서 UIActivityViewController.


* * 중요-이를 위해 시뮬레이터를 사용하지 마십시오. * *

2016 년에도 시뮬레이터는 앱에서 메일 보내기를 지원하지 않습니다.

실제로 시뮬레이터에는 메일 클라이언트가 없습니다.

그러나! 하단의 메시지를 확인하세요!


Henri가 전체 답변을 제공했습니다. 당신은

- 할당하고 초기 단계에 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, 아래 Michael Salamone의 오타 수정}

편의를 위해 접두사 파일에 다음 매크로가 있어야합니다.

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

또한 며칠이 걸릴 수있는 "사소한"문제가 있습니다. https://stackoverflow.com/a/17120065/294884


2016 FTR을 위해 APP에서 이메일을 보내는 기본 빠른 코드가 있습니다.

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

하나! 노트!

요즘에는 "앱에서"이메일을 보내는 것이 엉터리입니다.

오늘날에는 단순히 이메일 클라이언트로 전환하는 것이 훨씬 낫습니다.

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

These days, that is much better than trying to email from "inside" the app.

Remember again the iOS simulators simply do not have email clients (nor can you send email using the composer within an app). You must test on a device.


It has nothing to do with Swift. It's an issue with the mail composer that's been around forever it seems. That thing is extremely picky, from failing with timeouts to sending delegate messages even when cancelled.

The workaround everyone uses is to create a global mail composer (for example in a singleton), and every single time reinitializing it when you need it. This ensures the mail composer is always around when the OS needs it, but also that it is free of any crap when you want to reuse it.

So create a strong (as global as possible) variable holding the mail composer and reset it every time you want to use it.


  • XCode 6 Simulator has problems managing Mailcomposer and other things.
  • Try testing the code with a real device. Likely it will work.
  • I have problems when running MailComposer from actionSheet button, also with real test. With IOS 7 worked fine, the same code in IOS 8 does not work. For me Apple must depurated the XCode 6. ( too many different simulated devices with Objective-C and Swift together ...)

Not sure if the recycling proposed in above solution is necessary or not. But you do need use proper parameters.

The delegate receives a MFMailComposeViewController* parameter. And you need to use that instead of self when dismissing the controller. I.e.

대리인은 (MFMailComposeViewController *) controller. 그리고 당신 selfMFMailComposeViewController controller. 그것은 결국 당신이 무시하고 싶은 것입니다.

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

메일 작성기의 특성을 작성하고 메일 작성기가 필요할 때 호출하는 것보다로드 된보기에서 인스턴스화하십시오.

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

이 문제는 2 일 전에 출시 된 iOS 8.3으로 해결되었습니다.


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에 인스턴스 변수로 배치하고 필요할 때 호출하십시오.

참조 URL : https://stackoverflow.com/questions/25604552/i-have-real-misunderstanding-with-mfmailcomposeviewcontroller-in-swift-ios8-in

반응형