Skip to content

Instantly share code, notes, and snippets.

@stleamist
Last active June 2, 2021 13:28
Show Gist options
  • Save stleamist/6baaa33cdfeecbcb2ba449dfa29abd35 to your computer and use it in GitHub Desktop.
Save stleamist/6baaa33cdfeecbcb2ba449dfa29abd35 to your computer and use it in GitHub Desktop.
MFMailComposeViewController for SwiftUI
import MessageUI
import SwiftUI
struct MailComposer {
struct MessageBody {
var body: String
var isHTML: Bool
}
struct AttachmentData {
var data: Data
var mimeType: String
var fileName: String
}
var subject: String = ""
var toRecipients: [String]? = nil
var ccRecipients: [String]? = nil
var bccRecipients: [String]? = nil
var messageBody: MessageBody = .init(body: "", isHTML: false)
var attachmentData: [AttachmentData] = []
var preferredSendingEmailAddress: String = ""
var _onFinish: ((MFMailComposeResult, Error?) -> Void)? = nil
func onFinish(perform action: @escaping (MFMailComposeResult, Error?) -> Void) -> Self {
var modified = self
modified._onFinish = action
return modified
}
}
extension View {
func mailComposer(isPresented: Binding<Bool>, content: () -> MailComposer) -> some View {
self.modifier(MailComposeViewPresenter(isPresented: isPresented, mailComposer: content()))
}
}
private struct MailComposeViewPresenter: ViewModifier {
@Binding var isPresented: Bool
var mailComposer: MailComposer
func body(content: Content) -> some View {
if MFMailComposeViewController.canSendMail() {
content
.sheet(isPresented: $isPresented) {
MailComposeView(mailComposer: mailComposer)
.edgesIgnoringSafeArea(.all)
}
} else {
content
.alert(isPresented: $isPresented) {
Alert(
title: Text("Cannot Send Email"),
message: Text("Your device is not configured to send email.")
)
}
}
}
}
private struct MailComposeView: UIViewControllerRepresentable {
var mailComposer: MailComposer
@Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
func makeCoordinator() -> Coordinator {
return .init(representable: self)
}
func makeUIViewController(context: Context) -> UIViewController {
guard MFMailComposeViewController.canSendMail() else {
assertionFailure("Don't use `MailComposeView` directly and use `mailComposer(isPresented:content:)` instead.")
presentationMode.wrappedValue.dismiss()
return UIViewController()
}
let mailComposeViewController = MFMailComposeViewController()
mailComposeViewController.mailComposeDelegate = context.coordinator
// After presenting a mail compose view controller, the system ignores any attempts to modify the email using the methods of this class.
// The user can still edit the content of the email, but your app cannot.
// Therefore, always configure the fields of your email before presenting the view controller.
mailComposeViewController.setSubject(mailComposer.subject)
mailComposeViewController.setToRecipients(mailComposer.toRecipients)
mailComposeViewController.setCcRecipients(mailComposer.ccRecipients)
mailComposeViewController.setBccRecipients(mailComposer.bccRecipients)
mailComposeViewController.setMessageBody(mailComposer.messageBody.body, isHTML: mailComposer.messageBody.isHTML)
mailComposer.attachmentData.forEach { mailComposeViewController.addAttachmentData($0.data, mimeType: $0.mimeType, fileName: $0.fileName) }
mailComposeViewController.setPreferredSendingEmailAddress(mailComposer.preferredSendingEmailAddress)
return mailComposeViewController
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
context.coordinator.representable = self
}
class Coordinator: NSObject, MFMailComposeViewControllerDelegate {
var representable: MailComposeView
init(representable: MailComposeView) {
self.representable = representable
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
representable.mailComposer._onFinish?(result, error)
representable.presentationMode.wrappedValue.dismiss()
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment