SwiftUI: Send email SwiftUI: Send email swift swift

SwiftUI: Send email


@Matteo's answer is good but it needs to use the presentation environment variable. I have updated it here and it addresses all of the concerns in the comments.

import SwiftUIimport UIKitimport MessageUIstruct MailView: UIViewControllerRepresentable {    @Environment(\.presentationMode) var presentation    @Binding var result: Result<MFMailComposeResult, Error>?    class Coordinator: NSObject, MFMailComposeViewControllerDelegate {        @Binding var presentation: PresentationMode        @Binding var result: Result<MFMailComposeResult, Error>?        init(presentation: Binding<PresentationMode>,             result: Binding<Result<MFMailComposeResult, Error>?>) {            _presentation = presentation            _result = result        }        func mailComposeController(_ controller: MFMailComposeViewController,                                   didFinishWith result: MFMailComposeResult,                                   error: Error?) {            defer {                $presentation.wrappedValue.dismiss()            }            guard error == nil else {                self.result = .failure(error!)                return            }            self.result = .success(result)        }    }    func makeCoordinator() -> Coordinator {        return Coordinator(presentation: presentation,                           result: $result)    }    func makeUIViewController(context: UIViewControllerRepresentableContext<MailView>) -> MFMailComposeViewController {        let vc = MFMailComposeViewController()        vc.mailComposeDelegate = context.coordinator        return vc    }    func updateUIViewController(_ uiViewController: MFMailComposeViewController,                                context: UIViewControllerRepresentableContext<MailView>) {    }}

Usage:

import SwiftUIimport MessageUIstruct ContentView: View {   @State var result: Result<MFMailComposeResult, Error>? = nil   @State var isShowingMailView = false    var body: some View {        Button(action: {            self.isShowingMailView.toggle()        }) {            Text("Tap Me")        }        .disabled(!MFMailComposeViewController.canSendMail())        .sheet(isPresented: $isShowingMailView) {            MailView(result: self.$result)        }    }}struct ContentView_Previews: PreviewProvider {    static var previews: some View {        ContentView()    }}


As you mentioned, you need to port the component to SwiftUI via UIViewControllerRepresentable.

Here's a simple implementation:

struct MailView: UIViewControllerRepresentable {    @Binding var isShowing: Bool    @Binding var result: Result<MFMailComposeResult, Error>?    class Coordinator: NSObject, MFMailComposeViewControllerDelegate {        @Binding var isShowing: Bool        @Binding var result: Result<MFMailComposeResult, Error>?        init(isShowing: Binding<Bool>,             result: Binding<Result<MFMailComposeResult, Error>?>) {            _isShowing = isShowing            _result = result        }        func mailComposeController(_ controller: MFMailComposeViewController,                                   didFinishWith result: MFMailComposeResult,                                   error: Error?) {            defer {                isShowing = false            }            guard error == nil else {                self.result = .failure(error!)                return            }            self.result = .success(result)        }    }    func makeCoordinator() -> Coordinator {        return Coordinator(isShowing: $isShowing,                           result: $result)    }    func makeUIViewController(context: UIViewControllerRepresentableContext<MailView>) -> MFMailComposeViewController {        let vc = MFMailComposeViewController()        vc.mailComposeDelegate = context.coordinator        return vc    }    func updateUIViewController(_ uiViewController: MFMailComposeViewController,                                context: UIViewControllerRepresentableContext<MailView>) {    }}

Usage:

struct ContentView: View {    @State var result: Result<MFMailComposeResult, Error>? = nil    @State var isShowingMailView = false    var body: some View {        VStack {            if MFMailComposeViewController.canSendMail() {                Button("Show mail view") {                    self.isShowingMailView.toggle()                }            } else {                Text("Can't send emails from this device")            }            if result != nil {                Text("Result: \(String(describing: result))")                    .lineLimit(nil)            }        }        .sheet(isPresented: $isShowingMailView) {            MailView(isShowing: self.$isShowingMailView, result: self.$result)        }    }}

(Tested on iPhone 7 Plus running iOS 13 - works like a charm)

Updated for Xcode 11.4


Answers are correct Hobbes the Tige & Matteo

From the comments, if you need to show an alert if no email is set up on the button or tap gesture

@State var isShowingMailView = false@State var alertNoMail = false@State var result: Result<MFMailComposeResult, Error>? = nilHStack {                Image(systemName: "envelope.circle").imageScale(.large)                Text("Contact")            }.onTapGesture {                MFMailComposeViewController.canSendMail() ? self.isShowingMailView.toggle() : self.alertNoMail.toggle()            }                //            .disabled(!MFMailComposeViewController.canSendMail())                .sheet(isPresented: $isShowingMailView) {                    MailView(result: self.$result)            }            .alert(isPresented: self.$alertNoMail) {                Alert(title: Text("NO MAIL SETUP"))            }

To pre-populate To, Body ... also I add system sound same as Apple email sending sound

Parameters: recipients & messageBody can be injected when you init. MailView

import AVFoundationimport Foundationimport MessageUIimport SwiftUIimport UIKitstruct MailView: UIViewControllerRepresentable {    @Environment(\.presentationMode) var presentation    @Binding var result: Result<MFMailComposeResult, Error>?    var recipients = [String]()    var messageBody = ""    class Coordinator: NSObject, MFMailComposeViewControllerDelegate {        @Binding var presentation: PresentationMode        @Binding var result: Result<MFMailComposeResult, Error>?        init(presentation: Binding<PresentationMode>,             result: Binding<Result<MFMailComposeResult, Error>?>)        {            _presentation = presentation            _result = result        }        func mailComposeController(_: MFMailComposeViewController,                                   didFinishWith result: MFMailComposeResult,                                   error: Error?)        {            defer {                $presentation.wrappedValue.dismiss()            }            guard error == nil else {                self.result = .failure(error!)                return            }            self.result = .success(result)                        if result == .sent {            AudioServicesPlayAlertSound(SystemSoundID(1001))            }        }    }    func makeCoordinator() -> Coordinator {        return Coordinator(presentation: presentation,                           result: $result)    }    func makeUIViewController(context: UIViewControllerRepresentableContext<MailView>) -> MFMailComposeViewController {        let vc = MFMailComposeViewController()        vc.setToRecipients(recipients)        vc.setMessageBody(messageBody, isHTML: true)        vc.mailComposeDelegate = context.coordinator        return vc    }    func updateUIViewController(_: MFMailComposeViewController,                                context _: UIViewControllerRepresentableContext<MailView>) {}}