アプリからメーラを起動して、データを送る機能を作るときによく使うMFMailComposeViewControllerをSwiftUI向けにwrapしてみました。
Sponsor Link
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だけに設定しているとコールバックされません。
Sponsor Link