Intermediate iOS 15 Programming with Swift

Chapter 6
Working with Email and Attachments

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.

Starting with the Xcode Project Template

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.

Figure 6.1. The demo app showing a list of attachments
Figure 6.1. The demo app showing a list of attachments

Creating Email Using the MessageUI Framework

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:

  • Create an instance of 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.
  • Implement the 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:

  • The operation is cancelled.
  • The email is saved as a draft.
  • The email has been sent.
  • The email is failed to send.
  • The @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:

  • the data to attach – this is the content of a file that you want to attach in the form of Data.
  • the MIME type – the MIME type of the attachment (e.g. image/png).
  • the file name – that's the preferred file name to associate with the attachment.

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.

Figure 6.2. Displaying the mail interface inside the demo app
Figure 6.2. Displaying the mail interface inside the demo app

For reference, you can download the full source code from http://www.appcoda.com/resources/swift55/EmailAttachment.zip.