SwiftUI: Send email

前端 未结 6 1498
小蘑菇
小蘑菇 2020-12-08 02:06

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

let mailComposeViewController = configuredMailComposeViewController()

ma         


        
相关标签:
6条回答
  • 2020-12-08 02:46

    @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 SwiftUI
    import UIKit
    import MessageUI
    
    struct 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 SwiftUI
    import MessageUI
    
    struct 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()
        }
    }
    
    0 讨论(0)
  • 2020-12-08 02:48

    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

    0 讨论(0)
  • 2020-12-08 02:53

    Yeeee @Hobbes the Tige answer is good but...

    Let's make it even better! What if user doesn't have Mail app (like I don't). You can handle it by trying out other mail apps.

    if MFMailComposeViewController.canSendMail() {
       self.showMailView.toggle()
    } else if let emailUrl = Utils.createEmailUrl(subject: "Yo, sup?", body: "hot dog") {
       UIApplication.shared.open(emailUrl)
    } else {
       self.alertNoMail.toggle()
    }
    

    createEmailUrl

    static func createEmailUrl(subject: String, body: String) -> URL? {
            let to = YOUR_EMAIL
            let subjectEncoded = subject.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
            let bodyEncoded = body.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
    
            let gmailUrl = URL(string: "googlegmail://co?to=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
            let outlookUrl = URL(string: "ms-outlook://compose?to=\(to)&subject=\(subjectEncoded)")
            let yahooMail = URL(string: "ymail://mail/compose?to=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
            let sparkUrl = URL(string: "readdle-spark://compose?recipient=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
            let defaultUrl = URL(string: "mailto:\(to)?subject=\(subjectEncoded)&body=\(bodyEncoded)")
    
            if let gmailUrl = gmailUrl, UIApplication.shared.canOpenURL(gmailUrl) {
                return gmailUrl
            } else if let outlookUrl = outlookUrl, UIApplication.shared.canOpenURL(outlookUrl) {
                return outlookUrl
            } else if let yahooMail = yahooMail, UIApplication.shared.canOpenURL(yahooMail) {
                return yahooMail
            } else if let sparkUrl = sparkUrl, UIApplication.shared.canOpenURL(sparkUrl) {
                return sparkUrl
            }
    
            return defaultUrl
        }
    
    

    Info.plist

    <key>LSApplicationQueriesSchemes</key>
    <array>
        <string>googlegmail</string>
        <string>ms-outlook</string>
        <string>readdle-spark</string>
        <string>ymail</string>
    </array>
    
    0 讨论(0)
  • 2020-12-08 02:56

    Well, I have an old code that I used in SwiftUI in this way. The static function belongs to this class basically stays in my Utilities.swift file. But for demonstration purposes, I moved that in here.

    Also to retain the delegate and works correctly, I have used this one as a singleton pattern.

    Step 1: Create an Email Helper class

    import Foundation
    import MessageUI
    
    class EmailHelper: NSObject, MFMailComposeViewControllerDelegate {
        public static let shared = EmailHelper()
        private override init() {
            //
        }
        
        func sendEmail(subject:String, body:String, to:String){
            if !MFMailComposeViewController.canSendMail() {
                // Utilities.showErrorBanner(title: "No mail account found", subtitle: "Please setup a mail account")
                return //EXIT
            }
            
            let picker = MFMailComposeViewController()
            
            picker.setSubject(subject)
            picker.setMessageBody(body, isHTML: true)
            picker.setToRecipients([to])
            picker.mailComposeDelegate = self
            
            EmailHelper.getRootViewController()?.present(picker, animated: true, completion: nil)
        }
        
        func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
            EmailHelper.getRootViewController()?.dismiss(animated: true, completion: nil)
        }
        
        static func getRootViewController() -> UIViewController? {
            (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.window?.rootViewController
        }
    }
    

    Step 2: Just call this way in SwiftUI class

    Button(action: {
       EmailHelper.shared.sendEmail(subject: "Anything...", body: "", to: "")
     }) {
         Text("Send Email")
     }
    

    I am using this is in my SwiftUI based project.

    0 讨论(0)
  • 2020-12-08 02:57

    I've created a github repository for it. just add it to your project and use it like this:

    struct ContentView: View {
    
    @State var showMailSheet = false
    
    var body: some View {
        NavigationView {
            Button(action: {
                self.showMailSheet.toggle()
            }) {
                Text("compose")
            }
        }
        .sheet(isPresented: self.$showMailSheet) {
            MailView(isShowing: self.$showMailSheet,
                     resultHandler: {
                        value in
                        switch value {
                        case .success(let result):
                            switch result {
                            case .cancelled:
                                print("cancelled")
                            case .failed:
                                print("failed")
                            case .saved:
                                print("saved")
                            default:
                                print("sent")
                            }
                        case .failure(let error):
                            print("error: \(error.localizedDescription)")
                        }
            },
                     subject: "test Subjet",
                     toRecipients: ["recipient@test.com"],
                     ccRecipients: ["cc@test.com"],
                     bccRecipients: ["bcc@test.com"],
                     messageBody: "works like a charm!",
                     isHtml: false)
            .safe()
            
        }
    
      }
    }
    

    safe() modifier checks if MFMailComposeViewController.canSendMail() is false, it automatically dismesses the modal and tries to open a mailto link.

    0 讨论(0)
  • 2020-12-08 03:12

    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
    
    
    HStack {
                    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 ...

    func makeUIViewController(context: UIViewControllerRepresentableContext<MailView>) -> MFMailComposeViewController {
            let vc = MFMailComposeViewController()
            vc.setToRecipients(["your@mail.com"])
            vc.setMessageBody("<p>You're so awesome!</p>", isHTML: true)
            vc.mailComposeDelegate = context.coordinator
            return vc
        }
    
    0 讨论(0)
提交回复
热议问题