SwiftUI:发送电子邮件

Kha*_*inn 6 messageui swift mfmailcomposeviewcontroller swiftui

UIViewControllerSwift 的正常情况下,我使用此代码发送邮件。

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()
}
Run Code Online (Sandbox Code Playgroud)

如何在SwiftUI中实现相同目标?

我需要使用UIViewControllerRepresentable吗?

Hob*_*ige 67

@Matteo 的回答很好,但它需要使用演示环境变量。我在这里更新了它,它解决了评论中的所有问题。

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>) {

    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

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()
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 感谢您将所有内容如此完美地结合在一起——效果很好!如果想要预先填充“收件人:”和“抄送:”地址,以及主题行、正文和一些附加文件,这些参数在上面的代码中放在哪里? (2认同)

zdr*_*kin 27

答案是正确的 Hobbes the Tige & Matteo

从评论中,如果您需要在按钮或点击手势上未设置电子邮件时显示警报

@State var isShowingMailView = false
@State var alertNoMail = false
@State var result: Result<MFMailComposeResult, Error>? = nil

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"))
            }
Run Code Online (Sandbox Code Playgroud)

要预先填充 To, Body ... 我还添加了与 Apple 电子邮件发送声音相同的系统声音

参数:recipients & messageBody 可以在初始化时注入。邮件视图

import AVFoundation
import Foundation
import MessageUI
import SwiftUI
import UIKit

struct 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>) {}
}
Run Code Online (Sandbox Code Playgroud)


Mah*_*san 8

好吧,我有一个以这种方式在 SwiftUI 中使用的旧代码。属于这个类的静态函数基本上留在我的 Utilities.swift 文件中。但为了演示目的,我把它移到了这里。

同样为了保留委托并正常工作,我将其用作单例模式。

第 1 步:创建电子邮件助手类

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

         // OR If you use SwiftUI 2.0 based WindowGroup try this one
         // UIApplication.shared.windows.first?.rootViewController
    }
}
Run Code Online (Sandbox Code Playgroud)

第 2 步:只需在 SwiftUI 类中调用这种方式

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

我在我的基于 SwiftUI 的项目中使用它。


Ale*_*ace 8

对于像我这样的人来说,想要一个更好的解决方案而不会让用户的屏幕出现故障,我在 Medium 的这篇文章中找到了一个非常好的解决方案。该解决方案类似于@Mahmud Assan 的答案,但有更多电子邮件应用程序选项和错误应用程序警报。

我替换了一些代码以允许打开更多电子邮件应用程序,而不仅仅是 Mail 或 gmail。

首先,请记住在Info.plist中添加相应的信息,在我的例子中:

<key>LSApplicationQueriesSchemes</key>
<array>
    <string>googlegmail</string>
    <string>ms-outlook</string>
    <string>readdle-spark</string>
    <string>ymail</string>
</array>
Run Code Online (Sandbox Code Playgroud)

之后,您需要使用以下代码创建一个新的swift 文件:

import SwiftUI
import MessageUI

class EmailHelper: NSObject {
    /// singleton
    static let shared = EmailHelper()
    private override init() {}
}

extension EmailHelper {
    
    func send(subject: String, body: String, to: [String]) {
        
        let scenes = UIApplication.shared.connectedScenes
        let windowScene = scenes.first as? UIWindowScene
        
        guard let viewController = windowScene?.windows.first?.rootViewController else {
            return
        }
        
        if !MFMailComposeViewController.canSendMail() {
            let subjectEncoded = subject.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
            let bodyEncoded = body.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
            let mails = to.joined(separator: ",")
            
            let alert = UIAlertController(title: "Cannot open Mail!", message: "", preferredStyle: .actionSheet)
            
            var haveExternalMailbox = false
            
            if let url = createEmailUrl(to: mails, subject: subjectEncoded, body: bodyEncoded), UIApplication.shared.canOpenURL(url) {
                haveExternalMailbox = true
                alert.addAction(UIAlertAction(title: "Gmail", style: .default, handler: { (action) in
                    UIApplication.shared.open(url)
                }))
            }
            
            if haveExternalMailbox {
                alert.message = "Would you like to open an external mailbox?"
            } else {
                alert.message = "Please add your mail to Settings before using the mail service."
                
                if let settingsUrl = URL(string: UIApplication.openSettingsURLString),
                   UIApplication.shared.canOpenURL(settingsUrl) {
                    alert.addAction(UIAlertAction(title: "Open Settings App", style: .default, handler: { (action) in
                        UIApplication.shared.open(settingsUrl)
                    }))
                }
            }
            
            alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
            viewController.present(alert, animated: true, completion: nil)
            return
        }
        
        let mailCompose = MFMailComposeViewController()
        mailCompose.setSubject(subject)
        mailCompose.setMessageBody(body, isHTML: false)
        mailCompose.setToRecipients(to)
        mailCompose.mailComposeDelegate = self
        
        viewController.present(mailCompose, animated: true, completion: nil)
    }
    
    private func createEmailUrl(to: String, subject: String, body: String) -> URL? {
        let subjectEncoded = subject.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
        let bodyEncoded = body.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
        
        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
    }
    
}

// MARK: - MFMailComposeViewControllerDelegate
extension EmailHelper: MFMailComposeViewControllerDelegate {
    
    func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
        controller.dismiss(animated: true, completion: nil)
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,转到您想要实现此操作的视图:

  struct OpenMailView: View {
    var body: some View {
        Button("Send email") {
            EmailHelper.shared.send(subject: "Help", body: "", to: ["email@gmail.com"])
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Ene*_*man 7

我还改进了@Hobbes 的答案,以便轻松配置主题、收件人等参数。

检查这个要点

即使懒得结帐要点,那么SPM呢?

您现在可以轻松地将这份礼物复制粘贴到不同的项目中。

用法;

import SwiftUI
import MessagesUI
// import SwiftUIEKtensions // via SPM

@State private var result: Result<MFMailComposeResult, Error>? = nil
@State private var isShowingMailView = false

var body: some View {
    Form {
        Button(action: {
            if MFMailComposeViewController.canSendMail() {
                self.isShowingMailView.toggle()
            } else {
                print("Can't send emails from this device")
            }
            if result != nil {
                print("Result: \(String(describing: result))")
            }
        }) {
            HStack {
                Image(systemName: "envelope")
                Text("Contact Us")
            }
        }
        // .disabled(!MFMailComposeViewController.canSendMail())
    }
    .sheet(isPresented: $isShowingMailView) {
        MailView(result: $result) { composer in
            composer.setSubject("Secret")
            composer.setToRecipients(["fancy@mail.com"])
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Mat*_*ini 6

如前所述,您需要将组件移植到SwiftUIvia UIViewControllerRepresentable

这是一个简单的实现:

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>) {

    }
}
Run Code Online (Sandbox Code Playgroud)

用法

struct ContentView: View {

    @State var result: Result<MFMailComposeResult, Error>? = nil
    @State var isShowingMailView = false

    var body: some View {
        ZStack {
            VStack {
                Button(action: {
                    self.isShowingMailView.toggle()
                }) {
                    Text("Show mail view")
                }
                if result != nil {
                    Text("Result: \(String(describing: result))")
                    .lineLimit(nil)
                }
            }
            if (isShowingMailView) {
                mailView()
                .transition(.move(edge: .bottom))
                .animation(.default)
            }
        }


    }

    private func mailView() -> some View {
        MFMailComposeViewController.canSendMail() ?
            AnyView(MailView(isShowing: $isShowingMailView, result: $result)) :
            AnyView(Text("Can't send emails from this device"))
    }
}
Run Code Online (Sandbox Code Playgroud)

注意事项

我使用a ZStack来显示它,因为Modal行为非常不一致。

(已在运行iOS 13的iPhone 7 Plus上进行了测试-就像一个护身符)

更新了Xcode 11 beta 5


Ste*_*Lee 6

Yeeee @Hobbes the Tige 答案很好,但是...

让我们让它变得更好!如果用户没有邮件应用程序(就像我没有)怎么办。您可以通过尝试其他邮件应用程序来处理它。

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()
}
Run Code Online (Sandbox Code Playgroud)

创建电子邮件地址

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
    }

Run Code Online (Sandbox Code Playgroud)

信息表

<key>LSApplicationQueriesSchemes</key>
<array>
    <string>googlegmail</string>
    <string>ms-outlook</string>
    <string>readdle-spark</string>
    <string>ymail</string>
</array>
Run Code Online (Sandbox Code Playgroud)

  • 这是 /sf/answers/3903575371/ 的完整副本。如果您参考的话,请留下另一个答案的链接 (5认同)

bat*_*rbb 6

我升级并简化了 @Mahmud Assan 对新SwiftUI Lifecycle的回答。

import Foundation
import MessageUI

class EmailService: NSObject, MFMailComposeViewControllerDelegate {
public static let shared = EmailService()

func sendEmail(subject:String, body:String, to:String, completion: @escaping (Bool) -> Void){
 if MFMailComposeViewController.canSendMail(){
    let picker = MFMailComposeViewController()
    picker.setSubject(subject)
    picker.setMessageBody(body, isHTML: true)
    picker.setToRecipients([to])
    picker.mailComposeDelegate = self
    
   UIApplication.shared.windows.first?.rootViewController?.present(picker,  animated: true, completion: nil)
}
  completion(MFMailComposeViewController.canSendMail())
}

func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
    controller.dismiss(animated: true, completion: nil)
     }
}
Run Code Online (Sandbox Code Playgroud)

用法:

Button(action: {
            EmailService.shared.sendEmail(subject: "hello", body: "this is body", to: "asd@gmail.com") { (isWorked) in
                if !isWorked{ //if mail couldn't be presented
                    // do action
                }
            }
        }, label: {
            Text("Send Email")
        })
Run Code Online (Sandbox Code Playgroud)