MFMailComposeViewControllerを使用してメールを送信する方法を説明します。

基本的に簡単に使えるはず…ですが、iOS 8以降シミュレーターがバグっていて、シミュレーター上で実行するとクラッシュする不具合が発生しているようです(iOS 9でも確認)。

テストする場合iOSデバイスの実機が必須かもしれません。

MessageUI.frameworkの追加

MFMailComposeViewControllerを使用するには、まず始めにMessageUI.frameworkを追加する必要があります。

Xcode

「TARGETS」を選択し「Build Phases」タブの「Link Binary With Libraries」からMessageUI.frameworkを追加します。

理想のサンプルコード

メール送信の基本的なソースコードを掲載します。

class ViewController: UIViewController,MFMailComposeViewControllerDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    
    @IBAction func mailTapped(sender: AnyObject) {
        //メールが送信できるかどうかの確認は必ず必要
        if MFMailComposeViewController.canSendMail() {
            let e = MFMailComposeViewController()
            e.mailComposeDelegate = self //delegateの設定
            e.setToRecipients(["test@swift-study.com"]) //宛先アドレス
            e.setCcRecipients(["cc@swift-study.com"]) //CCアドレス
            e.setBccRecipients(["bcc@swift-study.com"]) //BCCアドレス
            e.setSubject("テストサブジェクトです") //サブジェクト
            e.setMessageBody("テスト本文です", isHTML: false) //メール本文
            presentViewController(e, animated: true, completion: nil) //メール作成画面呼び出し
        } else {
            print("送信できません")
        }
    }
    
    func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) {
        if result == MFMailComposeResultCancelled {
            print("メール送信がキャンセルされました")
        } else if result == MFMailComposeResultSaved {
            print("下書きとして保存されました")
        } else if result == MFMailComposeResultSent {
            print("メール送信に成功しました")
        } else if result == MFMailComposeResultFailed {
            print("メール送信に失敗しました")
        }
        dismissViewControllerAnimated(true, completion: nil) //閉じる
    }
}

iOSデバイスの実機でメールアカウントの設定が行われているならば、このコードを実行すると、ページ冒頭にあるようなメール作成画面が立ち上がるはずです(適当にボタンを一個はりつけてそのボタンをタップしたときに起動することを想定しています)。

ただしシミュレーターで実行すると以下のようなエラーが表示されてクラッシュします。

S 2016 07 29 15 16 55

調べるとiOS 8以降、iOS 9でもシミュレーターで実行するとこのエラーが発生しました。

シミュレーターエラーの回避は不可能?

このエラーに関してはstackoverflowはじめさまざまな場所で議論が行われており、シミュレーターの不具合として認識されているようです。

  • [MFMailComposeViewControllerでメール送信しようとするとviewServiceDidTerminateWithErrorが発生してしまう][1]
    • [I have REAL misunderstanding with MFMailComposeViewController in Swift (iOS8) in Simulator][2]

    「MFMailComposeViewControllerを初期化してすぐに使うと発生する問題で、MFMailComposeViewControllerインスタンスをアプリ起動後すぐに生成し、メール送信後はインスタンスを作成しなおす方法が有効」という情報もありましたが、iOS 9のシミュレーターで確認したところ効果ありませんでした。

    具体的には以下のEmailManagerというヘルパークラスを追加し、

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で初期化。
        
        ```swift
    var emailManager: EmailManager!


    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        emailManager = EmailManager()
        return true
    }
    ViewControllerで呼び出す。
    
    ```swift
@IBAction func mail2Tapped(sender: AnyObject) {
    let delegate = UIApplication.sharedApplication().delegate as! AppDelegate
    delegate.emailManager.sendMailTo(["test@swift-study.com"], subject: "テストサブジェクト2です", body: "テスト本文2です", fromViewController: self)
}
        
        と変更したのですが効果なしでした。
        
        シミュレーターの不具合ということでこれ以上深追いしても益はないかもしれません。なおこの状態で審査に提出しても問題はない模様。

 [1]: http://ja.stackoverflow.com/questions/6940/mfmailcomposeviewcontroller%E3%81%A7%E3%83%A1%E3%83%BC%E3%83%AB%E9%80%81%E4%BF%A1%E3%81%97%E3%82%88%E3%81%86%E3%81%A8%E3%81%99%E3%82%8B%E3%81%A8viewservicedidterminatewitherror%E3%81%8C%E7%99%BA%E7%94%9F%E3%81%97%E3%81%A6%E3%81%97%E3%81%BE%E3%81%86
 [2]: http://stackoverflow.com/questions/25604552/i-have-real-misunderstanding-with-mfmailcomposeviewcontroller-in-swift-ios8-in