1

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?

Philipp
  • 183
  • 1
  • 9

1 Answers1

3

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")
})
Raja Kishan
  • 16,767
  • 2
  • 26
  • 52
  • This isn't really a SwiftUI solution. Instead of creating a singleton `UIViewController`, you should wrap `MFMailComposeViewController` in a `UIViewControllerRepresentable`. – Dávid Pásztor Jan 04 '21 at 12:32
  • Thank you! Xcode does not show these errors anymore, though nothing happens when tapping the button to send it... – Philipp Jan 04 '21 at 12:57
  • @DávidPásztor Would you mind explaining this idea a little further, please? :) – Philipp Jan 04 '21 at 12:59
  • Here's the output: ```2021-01-04 16:01:38.850810+0300 PortfolioApp[10881:2184453] [AXRuntimeCommon] AX Lookup problem - errorCode:1100 error:Permission denied portName:'com.apple.iphone.axserver' PID:10833``` – Philipp Jan 04 '21 at 13:02
  • @DávidPásztor yes you are right. Please check my updated answere – Raja Kishan Jan 04 '21 at 13:12
  • 1
    @PhilippLazarev can you please check my updated answere – Raja Kishan Jan 04 '21 at 13:12
  • @RajaKishan this code crushes simulator when tapping the button, though works just fine when testing on a real device. So I guess it's the right behaviour. Thank you very much! – Philipp Jan 04 '21 at 13:19
  • 2
    It because in simulator does not have mail app. – Raja Kishan Jan 04 '21 at 13:23