18
May
2021
Send mail in SwiftUI
Reading time: 2 min
This recipe shows how to send mail from SwiftUI. You'll build a MailView
by wrapping MessageUI's MFMailComposeViewController
in a UIViewControllerRepresentable
. You'll be able to:
- Determine if you can send mail or not.
- Pass subject, message and recipients to the view via a binding.
- Attach files to the email.
- Receive success or failure result after sending the email.
The end result will look like this.
This component is available as a Swift Package in this repo.
OK, first define the models for the data you'll pass to the MailView. These correspond to parameters that you can set on a MFMailComposeViewController
:
struct ComposeMailData {
let subject: String
let recipients: [String]?
let message: String
let attachments: [AttachmentData]?
}
struct AttachmentData {
let data: Data
let mimeType: String
let fileName: String
}
Then, add the wrapper code:
import SwiftUI
import UIKit
import MessageUI
typealias MailViewCallback = ((Result<MFMailComposeResult, Error>) -> Void)?
struct MailView: UIViewControllerRepresentable {
@Environment(\.presentationMode) var presentation
@Binding var data: ComposeMailData
let callback: MailViewCallback
class Coordinator: NSObject, MFMailComposeViewControllerDelegate {
@Binding var presentation: PresentationMode
@Binding var data: ComposeMailData
let callback: MailViewCallback
init(presentation: Binding<PresentationMode>,
data: Binding<ComposeMailData>,
callback: MailViewCallback) {
_presentation = presentation
_data = data
self.callback = callback
}
func mailComposeController(_ controller: MFMailComposeViewController,
didFinishWith result: MFMailComposeResult,
error: Error?) {
if let error = error {
callback?(.failure(error))
} else {
callback?(.success(result))
}
$presentation.wrappedValue.dismiss()
}
}
func makeCoordinator() -> Coordinator {
Coordinator(presentation: presentation, data: $data, callback: callback)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<MailView>) -> MFMailComposeViewController {
let vc = MFMailComposeViewController()
vc.mailComposeDelegate = context.coordinator
vc.setSubject(data.subject)
vc.setToRecipients(data.recipients)
vc.setMessageBody(data.message, isHTML: false)
data.attachments?.forEach {
vc.addAttachmentData($0.data, mimeType: $0.mimeType, fileName: $0.fileName)
}
vc.accessibilityElementDidLoseFocus()
return vc
}
func updateUIViewController(_ uiViewController: MFMailComposeViewController,
context: UIViewControllerRepresentableContext<MailView>) {
}
static var canSendMail: Bool {
MFMailComposeViewController.canSendMail()
}
}
That's it! You can now use your new MailView
component like this:
struct MailViewTest: View {
@State private var mailData = ComposeMailData(subject: "A subject",
recipients: ["i.love@swiftuirecipes.com"],
message: "Here's a message",
attachments: [AttachmentData(data: "Some text".data(using: .utf8)!,
mimeType: "text/plain",
fileName: "text.txt")
])
@State private var showMailView = false
var body: some View {
Button(action: {
showMailView.toggle()
}) {
Text("Send mail")
}.disabled(!MailView.canSendMail)
.sheet(isPresented: $showMailView) {
MailView(data: $mailData) { result in
print(result)
}
}
}
}