swiftmfmailcomposeviewcontrollerswiftuimessageui

SwiftUI: Send email


In a normal UIViewController in Swift, I use this code to send a mail.

let mailComposeViewController = configuredMailComposeViewController()

mailComposeViewController.navigationItem.leftBarButtonItem?.style = .plain
mailComposeViewController.navigationItem.rightBarButtonItem?.style = .plain
mailComposeViewController.navigationBar.tintColor = UIColor.white

if MFMailComposeViewController.canSendMail() {
    self.present(mailComposeViewController, animated: true, completion: nil)
} else {
    self.showSendMailErrorAlert()
}

How can I achieve the same in SwiftUI?

Do I need to use UIViewControllerRepresentable?


Solution

  • 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