iosswiftemailswiftuimessageui

sending email with SwiftUI


I'm trying to implement sending email feature into my mini app.

Here's the code I'm using (took it from https://hackingwithswift.com):

import Foundation
import SwiftUI
import MessageUI

func sendEmail() {
    if MFMailComposeViewController.canSendMail() {
        let mail = MFMailComposeViewController()
        mail.mailComposeDelegate = self
        mail.setToRecipients(["you@yoursite.com"])
        mail.setMessageBody("<p>You're so awesome!</p>", isHTML: true)

        present(mail, animated: true)
    } else {
        // show failure alert
    }
}

func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
    controller.dismiss(animated: true)
}

When running my code, I get these 2 errors:

Cannot find self in scope

Cannot find present in scope

How can I fix it?


Solution

  • You can use UIViewControllerRepresentable

    MailComposeViewController

    struct MailComposeViewController: UIViewControllerRepresentable {
        
        var toRecipients: [String]
        var mailBody: String
        
        var didFinish: ()->()
        
        func makeCoordinator() -> Coordinator {
            return Coordinator(self)
        }
        
        func makeUIViewController(context: UIViewControllerRepresentableContext<MailComposeViewController>) -> MFMailComposeViewController {
            
            let mail = MFMailComposeViewController()
            mail.mailComposeDelegate = context.coordinator
            mail.setToRecipients(self.toRecipients)
            mail.setMessageBody(self.mailBody, isHTML: true)
            
            return mail
        }
        
        final class Coordinator: NSObject, MFMailComposeViewControllerDelegate {
            
            var parent: MailComposeViewController
            
            init(_ mailController: MailComposeViewController) {
                self.parent = mailController
            }
            
            func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
                parent.didFinish()
                controller.dismiss(animated: true)
            }
        }
        
        func updateUIViewController(_ uiViewController: MFMailComposeViewController, context: UIViewControllerRepresentableContext<MailComposeViewController>) {
            
        }
    }
    

    Usage:

    struct MailView: View {
        @State private var showingMail = false
        
        var body: some View {
            VStack {
                Button("Open Mail") {
                    self.showingMail.toggle()
                }
            }
            .sheet(isPresented: $showingMail) {
                MailComposeViewController(toRecipients: ["test@gmail.com"], mailBody: "Here is mail body") {
                    // Did finish action
                }
            }
        }
    }
    

    Possible another solution. You can create one singleton class and present MFMailComposeViewController on the root controller. You can modify function as per your requirement. Like this

    class MailComposeViewController: UIViewController, MFMailComposeViewControllerDelegate {
        
        static let shared = MailComposeViewController()
        
        func sendEmail() {
            if MFMailComposeViewController.canSendMail() {
                let mail = MFMailComposeViewController()
                mail.mailComposeDelegate = self
                mail.setToRecipients(["you@yoursite.com"])
                mail.setMessageBody("<p>You're so awesome!</p>", isHTML: true)
                UIApplication.shared.windows.first?.rootViewController?.present(mail, animated: true)
            } else {
                // show failure alert
            }
        }
        
        func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
            controller.dismiss(animated: true)
        }
    }
    

    Usage:

    Button(action: {
        MailComposeViewController.shared.sendEmail()
    }, label: {
        Text("Send")
    })