swiftmfmailcomposeviewcontroller

How to dismiss MFMailComposeViewController from class mail?


I have separated the mail functions from my UIviewController and placed them into a class ‘Mail’. Works fine, but now I do have trouble to dismiss my ‘MFMailComposeViewController’. The delegate ‘mailComposeController’ is not called, Any ideas how to fix?

import Foundation
import MessageUI


class Mail: UIViewController, MFMailComposeViewControllerDelegate{

    static func createMail2(
        fromViewController:UIViewController,
        recipients  :String,
        messageTitle:String,
        messageText :String,
        attachment  :AnyObject?)
    {

        
        if MFMailComposeViewController.canSendMail() {
            let mail = MFMailComposeViewController()
            //mail.mailComposeDelegate = self //Cannot assign value of type 'Mail.Type' to type 'MFMailComposeViewControllerDelegate?'
            mail.mailComposeDelegate? = fromViewController as! MFMailComposeViewControllerDelegate //added ? (optional)
            

            mail.setToRecipients([recipients])   //(["mail@to.me"])
            mail.setSubject(messageTitle)        //("your subject text")
            mail.setMessageBody(messageText, isHTML: false)
            
            //ggf. Attachment beifügen>>>
            if attachment != nil {
                //attachment vorhanden, also anhängen
                
                let attachName = "\(messageTitle).pdf"
                
                mail.addAttachmentData(
                    attachment as! Data,
                    mimeType: "application/octet-stream", //für binäre Daten, funktioniert immer
                    fileName: attachName)
            }//end if attachment
            //<<<ggf. Attachment beifügen

            
            // Present the view controller modally
            fromViewController.present(mail, animated: true)  //show mail
        } else {
            // show failure alert
            print("Mail services are not available")
            
            let alert = UIAlertController(title: "Mail error", message: "Your device has not been configured to send e-mails", preferredStyle: .alert)
            let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
            alert.addAction(okAction)
            fromViewController.present(alert,animated: true, completion: nil)
        }//end if else
    }//end func createMail
    
    
    //mail delegate
    func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
        //It’s called when the user dismisses the controller, either by sending the email or canceling the action. Either way, you have to dismiss the controller manually.
        
        //ggf. noch aktionen...
        
        controller.dismiss(animated: true) //remove the mail view
    }//end func mailComposeController
}//end class Mail

Solution

  • What you need is to create a static shared instance of your Mail controller and set if as the mailComposeDelegate. You should also create a controller property in your mail controller to keep a reference of the view controller that has invoked the mail composer and declare your createMail as an instance method (not static):

    import UIKit
    import MessageUI
    
    class MailComposer: NSObject, MFMailComposeViewControllerDelegate {
        static let shared = MailComposer()
        private var controller: UIViewController?
        func compose(controller: UIViewController, recipients: String,
                     messageTitle: String, messageText: String, fileURL: URL? = nil) { //, completion: @escaping ((MFMailComposeResult, Error?) -> Void)) {
            self.controller = controller
            if MFMailComposeViewController.canSendMail() {
                let mail = MFMailComposeViewController()
                mail.mailComposeDelegate = MailComposer.shared
                mail.setToRecipients([recipients])
                mail.setSubject(messageTitle)
                mail.setMessageBody(messageText, isHTML: false)
                if let fileURL = fileURL {
                    do {
                        try mail.addAttachmentData(Data(contentsOf: fileURL), mimeType: "application/octet-stream", fileName: fileURL.lastPathComponent)
                    } catch {
                        print(error)
                    }
                }
                controller.present(mail, animated: true)
            } else {
                let alertController = UIAlertController(title: "Mail Compose Error",
                    message: "Your device has not been configured to send e-mails",
                    preferredStyle: .alert)
                alertController.addAction(.init(title: "OK", style: .default) { _ in
                    alertController.dismiss(animated: true)
                })
                controller.present(alertController, animated: true)
            }
        }
        func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
            // You should switch the result only after dismissing the controller
            controller.dismiss(animated: true) {
                let message: String
                switch result {
                case .sent: message = "Message successfully sent!"
                case .saved: message = "Message saved!"
                case .cancelled: message = "Message Canceled!"
                case .failed:  message = "Unknown Error!"
                @unknown default: fatalError()
                }
                let alertController = UIAlertController(title: "Mail Composer",
                    message: message,
                    preferredStyle: .alert)
                alertController.addAction(.init(title: "OK", style: .default) { _ in
                    alertController.dismiss(animated: true)
                })
                self.controller?.present(alertController, animated: true) {
                    self.controller = nil
                }
                
            }
        }
    }
    

    Usage:

    class ViewController: UIViewController {
        override func viewDidLoad() {
               super.viewDidLoad()
        }
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
            let recipients = "user@email.com"
            let messageTitle = "New Message"
            let messageText = "Mail Test"
            MailComposer.shared.compose(controller: self, recipients: recipients, messageTitle: messageTitle, messageText: messageText)
        }
    }