[SwiftUI] MFMailComposeViewControllerをSwiftUI向けにWrapする

SwiftUI

アプリからメーラを起動して、データを送る機能を作るときによく使うMFMailComposeViewControllerをSwiftUI向けにwrapしてみました。

MFMailComposeViewControllerをWrapするクラス作成

定義は、class MFMailComposeViewController : UINavigationController となっているので、UIViewControllerRepresentableの出番です。

例によって、UIViewControllerRepresentableを継承するstructを作り、
typealias UIViewControllerType = MFMailComposeViewController を記述しました。

struct WrappedMFMailComposeViewController: UIViewControllerRepresentable {
    typealias UIViewControllerType = MFMailComposeViewController
}

記述してから、XCodeに不足しているメソッドのテンプレを作ってもらいました。

struct WrappedMFMailComposeViewController: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> MFMailComposeViewController {
    }
    func updateUIViewController(_ uiViewController: MFMailComposeViewController, context: Context) {
    }
    typealias UIViewControllerType = MFMailComposeViewController
}

Coordinator等作成

MFMailComposeViewControllerのDelegateにもなるCoordinatorを作成して、makeUIViewControllerでMFMailComposeViewControllerを返すときには、coordinatorをセットする。
合わせて、送信先等の設定も行います。

以下のコードは、メールにCSVファイルを添付することを決め打ちして作ったコードです。

struct WrappedMFMailComposeViewController: UIViewControllerRepresentable {
    @Environment(\.presentationMode) var presentationMode

    var mailRecipients:[String] = []
    var mailSubject:String = ""
    var mailBody:String = ""
    var mailAttachmentCSV:String = ""
    
    func makeUIViewController(context: Context) -> MFMailComposeViewController {
        let viewController = MFMailComposeViewController()
        viewController.delegate = context.coordinator
        viewController.mailComposeDelegate = context.coordinator
        viewController.setToRecipients(mailRecipients)
        viewController.setSubject(mailSubject)
        viewController.setMessageBody(mailBody, isHTML: false)
        if let data = mailAttachmentCSV.data(using: .utf8, allowLossyConversion: false) {
            viewController.addAttachmentData(data, mimeType: "text/csv", fileName: "filename.csv")
        }
        return viewController
    }
    func updateUIViewController(_ uiViewController: MFMailComposeViewController, context: Context) {
    }
    func makeCoordinator() -> Coordinator {
        let coordinator = Coordinator(self)
        return coordinator
    }
    class Coordinator: NSObject, MFMailComposeViewControllerDelegate, UINavigationControllerDelegate {
        var parent: WrappedMFMailComposeViewController
        
        init(_ parent: WrappedMFMailComposeViewController) {
            self.parent = parent
        }
        func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
            switch result {
            case .sent:
                print("send")
            case .cancelled:
                print("cancelled")
            case .failed:
                print("failed")
            case .saved:
                print("saved")
            default:
                print("default")
            }
            parent.presentationMode.wrappedValue.dismiss()
        }
    
    }
}




使う側

例えば以下のようなコードでボタンを押すと開くようになります。

Button("Backup") {
    if MFMailComposeViewController.canSendMail() {
        self.isShowingMFMailComposeView = true
    } else {
        self.isShowingAlert = true
    }
}
.sheet(isPresented: $isShowingMFMailComposeView ) {
    WrappedMFMailComposeViewController(mailRecipients: ["mail@address"], mailSubject: "csv data file",
                                       mailBody: "here you are", mailAttachmentCSV: "1,2,3\n4,5,6")
}
.alert(isPresented: $isShowingAlert ) {
    Alert(title: Text("can not send email on this device"))
}

ハマりやすい点

注意その1
シミュレータは、メールを送れないので、クラッシュします。
メール送信できるかの判断は、以下のコードで可能です。

MFMailComposeViewController.canSendMail()

上記で、disabled設定したり、開くシートを変えたりする必要があります。

注意その2
以下の2つのDelegateがあるので注意

MFMailComposeViewController.delegate
ビュー操作関連のDelegate
MFMailComposeViewController.mailComposeDelegate
メール操作関連のDelegate

送信したとか、キャンセルされたとかは、mailComposeDelegateの方に届くので、delegateだけに設定しているとコールバックされません。




コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です