The MessageUI
framework has made it really easy to send an email from your apps. You can easily use the built-in APIs to integrate an email composer in your app. In this short chapter, we'll show you how to send emails and work with email attachments by creating a simple app.
Since the primary focus is to demonstrate the email feature of the MessageUI
framework, we will keep the demo app very simple. The app simply displays a list of files in a plain table view. We'll populate the table with various types of files, including images in both PNG and JPEG formats, a Microsoft Word document, a Powerpoint file, a PDF document, and an HTML file. Whenever users tap on any of the files, the app automatically creates an email with the selected file as an attachment.
To save you from creating the Xcode project from scratch, you can download the project template from http://www.appcoda.com/resources/swift55/EmailAttachmentStarter.zip to begin the development. The project template comes with:
After downloading and extracting the zipped file, you can compile and run the project. The demo app should display a list of files on the main screen. Now, we'll continue to implement the email feature.
To present a standard composition interface for email in your app, you will need to use the APIs provided by the Message UI framework. Here is how you use the framework to let you compose an email without leaving your app:
MFMailComposeViewController
and display it inside your app. The MFMailComposeViewController
class provides a standard user interface for managing and sending an email. The UI is exactly as the one in the stock Mail app.MFMailComposeViewControllerDelegate
protocol to manage the mail composition. Say, what happens if the user fails to send the email? Also, it is your responsibility to dismiss the mail composer interface.Okay, now let's go into the implementation.
First, import the MessageUI
framework at the very beginning of AttachmentTableViewController.swift
:
import MessageUI
Next, we will use an extension to adopt and implement the MFMailComposeViewControllerDelegate
protocol. Your code will be like this:
extension AttachmentTableViewController: MFMailComposeViewControllerDelegate {
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
switch result {
case MFMailComposeResult.cancelled:
print("Mail cancelled")
case MFMailComposeResult.saved:
print("Mail saved")
case MFMailComposeResult.sent:
print("Mail sent")
case MFMailComposeResult.failed:
print("Failed to send: \(error?.localizedDescription ?? "")")
@unknown default:
print("Unknown error: \(error.debugDescription)")
}
dismiss(animated: true, completion: nil)
}
}
The mailComposeController(_:didFinishWith:error:)
method is called when a user dismisses the mail composer. There are a few scenarios it may happen when the mail interface is dismissed:
@unknown default
case was introduced in Swift 5. This case is designed to prepare for future changes. In the future, Apple will probably add a new enum case for MFMailComposeResult
. With @unknown default
, this ensures your code can still compile even if Apple introduces some new enum cases.The result
variable stores one of the possible scenarios at the time the mail composer is dismissed. We implement the method to handle the result. For demo purposes, however, we just log the mail result and dismiss the mail controller. In real world apps, you can display an alert message if the mail fails to send.
Next, declare an enumeration for the MIME types in the AttachmentTableViewController
class:
enum MIMEType: String {
case jpg = "image/jpeg"
case png = "image/png"
case doc = "application/msword"
case ppt = "application/vnd.ms-powerpoint"
case html = "text/html"
case pdf = "application/pdf"
init?(type: String) {
switch type.lowercased() {
case "jpg": self = .jpg
case "png": self = .png
case "doc": self = .doc
case "ppt": self = .ppt
case "html": self = .html
case "pdf": self = .pdf
default: return nil
}
}
}
MIME stands for Multipurpose Internet Mail Extensions. In short, MIME is an Internet standard that defines the way to send other kinds of information (e.g. graphics) through email. The MIME type indicates the type of data to attach. For instance, the MIME type of a PNG image is image/png. You can refer to the full list of MIME types at http://www.iana.org/assignments/media-types/.
Enumerations are particularly useful for storing a group of related values. Therefore, we use an enumeration to store the possible MIME types of the attachments. In Swift, you declare an enumeration with the enum
keyword and use the case
keyword to introduce new enumeration cases. Optionally, you can assign a raw value for each case. In the above code, we define the possible types of the files and assign each case with the corresponding MIME type.
In Swift, you define initializers in enumerations to provide an initial case value. In the above initialization, we take in a file type/extension (e.g. jpg) and look up for the corresponding case. Later, we will use this enumeration when creating the MFMailComposeViewController
object.
Now, create the methods for displaying the mail composer. Insert the following code in the same class:
func showEmail(attachment: String) {
// Check if the device is capable to send email
guard MFMailComposeViewController.canSendMail() else {
print("This device doesn't allow you to send mail.")
return
}
let emailTitle = "Great Photo and Doc"
let messageBody = "Hey, check this out!"
let toRecipients = ["support@appcoda.com"]
// Initialize the mail composer and populate the mail content
let mailComposer = MFMailComposeViewController()
mailComposer.mailComposeDelegate = self
mailComposer.setSubject(emailTitle)
mailComposer.setMessageBody(messageBody, isHTML: false)
mailComposer.setToRecipients(toRecipients)
// Determine the file name and extension
let fileparts = attachment.components(separatedBy: ".")
let filename = fileparts[0]
let fileExtension = fileparts[1]
// Get the resource path and read the file using NSData
guard let filePath = Bundle.main.path(forResource: filename, ofType: fileExtension) else {
return
}
// Get the file data and MIME type
if let fileData = try? Data(contentsOf: URL(fileURLWithPath: filePath)),
let mimeType = MIMEType(type: fileExtension) {
// Add attachment
mailComposer.addAttachmentData(fileData, mimeType: mimeType.rawValue, fileName: filename)
// Present mail view controller on screen
present(mailComposer, animated: true, completion: nil)
}
}
The showEmail
method takes an attachment, which is the file name of the attachment. At the very beginning, we check to see if the device is capable of sending email using the MFMailComposeViewController.canSendMail()
method.
Once we get a positive result, we instantiate an MFMailComposeViewController
object and populate it with some initial values including the email subject, message content, and the recipient email. The MFMailComposeViewController
class provides the standard user interface for managing the editing and sending of an email message. Later, when it is presented, you will see the predefined values in the mail message.
To add an attachment, all you need to do is call up the addAttachmentData
method of the MFMailComposeViewController
class.
mailComposer.addAttachmentData(fileData, mimeType: mimeType.rawValue, fileName: filename)
The method accepts three parameters:
Data
.The rest of the code in the showEmail
method is used to determine the values of these parameters.
First, we determine the path of the given attachment by using the path(forResource:ofType:)
method of Bundle
. In iOS, a Bundle
object represents a location in the file system of a resource group. In general, you use the main bundle to locate the directory of the current application executable. Since our resource files are embedded in the app, we retrieve the main bundle object, and then call the path(forResource:ofType:)
method to retrieve the path of the attachment.
The last block of the code is the core part of the method.
// Get the file data and MIME type
if let fileData = try? Data(contentsOf: URL(fileURLWithPath: filePath)),
let mimeType = MIMEType(type: fileExtension) {
// Add attachment
mailComposer.addAttachmentData(fileData, mimeType: mimeType.rawValue, fileName: filename)
// Present mail view controller on screen
present(mailComposer, animated: true, completion: nil)
}
Base on the file path, we instantiate a Data
object. The initialization of Data
may throw an exception. Here we use the try?
keyword to handle an error by converting it to an optional value. In other words, if there are any problems loading the file, a nil
value will be returned.
Once we initialized the Data
object, we determine the MIME type of the given file with respect to its file extension. As you can see from the above code, you are allowed to combine multiple if let
statements into one. Multiple optional bindings are separated by commas.
if let fileData = try? Data(contentsOf: URL(fileURLWithPath: filePath)),
let mimeType = MIMEType(type: fileExtension) {
}
Once we successfully initialized the file data and MIME type, we call the addAttachmentData(_:mimeType:fileName:)
method to attach the file and then present the mail composer.
We're almost ready to test the app. The app should bring up the mail interface when any of the files are selected. Thus, the last thing is to add the tableView(_:didSelectRowAt:)
method:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedFile = filenames[indexPath.row]
showEmail(attachment: selectedFile)
}
You're good to go. Compile and run the app on a real iOS device (NOT the simulators). Tap a file and the app should display the mail interface with your selected file attached.
For reference, you can download the full source code from http://www.appcoda.com/resources/swift55/EmailAttachment.zip.