I’m a fan of extensions – but hopefully only when it makes sense. I wanted to write an extension on UIViewController to allow me to send email from any subclass…
Swift: Compose Email (MFMailComposeViewController) as an Extension to UIViewController
Steps…
- Add a new file (⌘-N) and select a iOS>Source>Swift file (name it whatever you want)
- Add the code below as it’s list (full code at the bottom)
- Call from your class to create and present the composition controller
Start by declaring the extension on UIView Controller and adding the method that will create the composition view controller: MFMailComposeViewController
NOTE: I’m not closing the extension (with the } ) because more code will be added.
The method takes option recipients of strings, a subject, body, optional isHTML (with default of false) and an optional array of images to attach.
It creates the MFMailComposeViewController, sets the values passed in, attaches the images and returns it. You could change the attachments to be data instances or something if you’d rather.
import Foundation import MessageUI extension UIViewController : MFMailComposeViewControllerDelegate { func configuredMailComposeViewController(recipients : [String]?, subject : String, body : String, isHtml : Bool = false, images : [UIImage]?) -> MFMailComposeViewController { let mailComposerVC = MFMailComposeViewController() mailComposerVC.mailComposeDelegate = self // IMPORTANT mailComposerVC.setToRecipients(recipients) mailComposerVC.setSubject(subject) mailComposerVC.setMessageBody(body, isHTML: isHtml) for img in images ?? [] { if let jpegData = img.jpegData(compressionQuality: 1.0) { mailComposerVC.addAttachmentData(jpegData, mimeType: "image/jpg", fileName: "Image") } } return mailComposerVC } }
It doesn’t present the MFMailComposeViewController in case you want to do something else with it like adding cc or bcc emails, etc.
The second function presents the MFMailComposeViewController. First it checks to make sure the device can send email.
func presentMailComposeViewController(mailComposeViewController : MFMailComposeViewController) { if MFMailComposeViewController.canSendMail() { self.present(mailComposeViewController, animated: true, completion: nil) } else { let sendMailErrorAlert = UIAlertController.init(title: "Error", message: "Unable to send email. Please check your email " + "settings and try again.", preferredStyle: .alert) self.present(sendMailErrorAlert, animated: true, completion: nil) } }
The third function handles the return. It either dismisses the controller and it’s done or it displays an alert that there was an error.
public func mailComposeController(controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { switch (result) { case .cancelled: self.dismiss(animated: true, completion: nil) case .sent: self.dismiss(animated: true, completion: nil) case .failed: self.dismiss(animated: true, completion: { let sendMailErrorAlert = UIAlertController.init(title: "Failed", message: "Unable to send email. Please check your email " + "settings and try again.", preferredStyle: .alert) sendMailErrorAlert.addAction(UIAlertAction.init(title: "OK", style: .default, handler: nil)) self.present(sendMailErrorAlert, animated: true, completion: nil) }) default: break; } }
That’s it!
I call it like this:
@IBAction func doBtnEmail(sender: AnyObject) { let toRecipients = ["support@example.com"] let subject = "Feedback" let body = "Enter comments here...<br><br><p>I have a \(versionText()).<br> And iOS version \(UIDevice.current.systemVersion).<br</p>" let mail = configuredMailComposeViewController(recipients: toRecipients, subject: subject, body: body, isHtml: true, images: nil) presentMailComposeViewController(mailComposeViewController: mail) }
I have one recipient (me), a subject and body (no attachments in this case). I use a method to get the version text (included in the full code below) and the version of iOS so the email (for support) tells me what they’re running. They could delete that, but…
Here’s the full code:
import Foundation import MessageUI extension UIViewController : MFMailComposeViewControllerDelegate { func configuredMailComposeViewController(recipients : [String]?, subject : String, body : String, isHtml : Bool = false, images : [UIImage]?) -> MFMailComposeViewController { let mailComposerVC = MFMailComposeViewController() mailComposerVC.mailComposeDelegate = self // IMPORTANT mailComposerVC.setToRecipients(recipients) mailComposerVC.setSubject(subject) mailComposerVC.setMessageBody(body, isHTML: isHtml) for img in images ?? [] { if let jpegData = img.jpegData(compressionQuality: 1.0) { mailComposerVC.addAttachmentData(jpegData, mimeType: "image/jpg", fileName: "Image") } } return mailComposerVC } func presentMailComposeViewController(mailComposeViewController : MFMailComposeViewController) { if MFMailComposeViewController.canSendMail() { self.present(mailComposeViewController, animated: true, completion: nil) } else { let sendMailErrorAlert = UIAlertController.init(title: "Error", message: "Unable to send email. Please check your email " + "settings and try again.", preferredStyle: .alert) self.present(sendMailErrorAlert, animated: true, completion: nil) } } public func mailComposeController(controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { switch (result) { case .cancelled: self.dismiss(animated: true, completion: nil) case .sent: self.dismiss(animated: true, completion: nil) case .failed: self.dismiss(animated: true, completion: { let sendMailErrorAlert = UIAlertController.init(title: "Failed", message: "Unable to send email. Please check your email " + "settings and try again.", preferredStyle: .alert) sendMailErrorAlert.addAction(UIAlertAction.init(title: "OK", style: .default, handler: nil)) self.present(sendMailErrorAlert, animated: true, completion: nil) }) default: break; } } func versionText () -> String { let bundleVersionKey = "CFBundleShortVersionString" let buildVersionKey = "CFBundleVersion" if let version = Bundle.main.object(forInfoDictionaryKey: bundleVersionKey) { if let build = Bundle.main.object(forInfoDictionaryKey: buildVersionKey) { let version = "Version \(version) - Build \(build)" return version } } return "" } }