SwiftUI:发送电子邮件

79

在 Swift 中的一个普通 UIViewController 中,我使用以下代码发送邮件。

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()
}

我该如何在SwiftUI中实现相同的功能?

我需要使用UIViewControllerRepresentable吗?


也许在2019年进行此操作时还没有这个选项,但是Link视图对此是否可行呢? - spig
13个回答

98

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

    }
}

使用方法:

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()
    }
}

3
感谢您将所有内容恰当地整合在一起,它很有效!如果想要预填写收件人和抄送地址、主题、正文以及一些附件文件,这些参数应该放在上述代码的哪里,请问? - ConfusionTowers
1
@ConfusionTowers 在 let vc = MFMailComposeViewController() 之后,您可以进行任何您习惯的配置。 - Alex Curylo
1
值得一提的是,我花了几个小时尝试让这个解决方案生效。至于SwiftUI 2.0 / Xcode 12版本,被接受的答案是在我的特定情况下可行的解决方案 - 使用@Binding var isShowing: Bool而不是@Environment(\.presentationMode) var presentation - andrewbuilder
1
如果有人遇到保存草稿/删除草稿弹出窗口出现卡顿和键盘卡顿的问题,可以尝试在MailView sheet中添加.edgesIgnoringSafeArea(.bottom)来解决这个问题。 - sp4c38
它能够工作,但存在一个小故障,当数据被填充到用户界面中时,我们无法像UIKit实现那样向下滑动来解除视图。 - Roland Lariotte
显示剩余3条评论

71
正如您所提到的,您需要通过 UIViewControllerRepresentable 将组件移植到 SwiftUI

下面是一个简单的实现:

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

    }
}

用法:

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

    }

}

(在运行iOS 13的iPhone 7 Plus上测试-效果非常好)

更新至Xcode 11.4


1
哇..这看起来与使用Swift进行常规iOS应用程序开发非常不同。谢谢。它正在运行。 - Khant Thu Linn
1
好的。我在这里提供一个替代方案,不需要使用SwiftUI来控制主机邮件... https://gist.github.com/florentmorin/4be7ca70c973c29cbeebbed4e2ef20ba - Florent Morin
有关如何以适当的模态方式呈现此内容的任何更新吗?我仍然只能呈现一次。ZStack解决方案甚至更加不稳定,看起来非常糟糕,因为它不会将视图堆叠在iOS 13模态堆栈中。 - hoshy
请看下面我的回答。我修改了上面的代码以适应演示环境变量。 - Hobbes the Tige
1
如果有人遇到保存草稿/删除草稿弹窗出现延迟并且键盘卡顿的问题,可以尝试在MailView sheet中添加.edgesIgnoringSafeArea(.bottom)来解决这个问题,对我很有帮助。 - sp4c38
显示剩余6条评论

36

答案正确,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"))
            }

为了预先填写收件人To和邮件正文Body...我还加入了与苹果邮件发送声音相同的系统声音

参数:在初始化MailView时可以注入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>) {}
}

很好的额外细节! - forrest
太好了!我想添加最终的触感。如何在发送邮件时播放通常的声音? - Nicola Mingotti
伟大的简单和有序 - Vinayak Bhor

12

我有一段旧代码,我用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
    }
}

步骤二:在SwiftUI类中以这种方式调用

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

我在我的基于SwiftUI的项目中使用了这个。

哇,这是最好的。使用起来非常简单。完美地工作了。只需删除Utilities.showErrorBanner即可。 - thegathering
很遗憾,当您尝试连续两次打开邮件应用程序时,此解决方案会导致应用程序崩溃。该错误显示为“[PPT] Error creating the CFMessagePort needed to communicate with PPT”。 - Roland Lariotte
在iPad上也会崩溃,因为popoverPresentationController没有设置,但是当我设置它时,什么也没有呈现。 - Darren

11

我还改进了@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"])
        }
    }
}

提取邮件发送器(MFMailComposeViewController)是个不错的举措;谢谢! - Meet Vora

8

Yeeee,@Hobbes the Tige的回答不错,但是...

让它变得更好!如果用户没有Mail app(就像我一样),该怎么办?您可以尝试其他邮件应用来处理它。

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>

不错的补充!你说得对,不是每个人都使用苹果邮件应用程序!有趣的是,我没有安装苹果邮件应用程序,但 MFMailComposeViewController.canSendMail() 仍然返回 true。 - Dom
嗯,奇怪。如果你删除了邮件应用程序,它不应该返回 true。 - Stephen Lee
6
这是一篇关于IT的文章,以下是翻译内容:如果您想要在Python中使用多个进程或线程来并行处理任务,那么multiprocessing和threading模块是您需要了解的两个重要模块。multiprocessing模块允许您在不同的进程中运行代码,每个进程都有自己的内存空间和全局解释器锁(GIL)。这意味着您可以同时利用多个CPU核心来加速计算。但是,由于进程之间的通信需要额外的开销,因此在某些情况下,使用多个进程可能会比使用单个进程更慢。另一方面,threading模块允许您在同一进程中运行多个线程。由于所有线程共享相同的内存空间和GIL,因此线程之间的通信开销较小。但是,由于GIL的存在,只有一个线程可以执行Python字节码,因此在某些情况下,使用多个线程可能无法实现真正的并行性。因此,在选择使用哪种模块时,您需要考虑到您的具体需求和应用场景。如果您需要真正的并行性,并且可以承受额外的通信开销,则应该选择multiprocessing模块。如果您只需要在同一进程中运行多个任务,并且希望减少通信开销,则应该选择threading模块。如果您想要了解更多关于Python并行编程的内容,可以参考这篇文章:https://dev59.com/TXA75IYBdhLWcg3w6Ndj - ramzesenok
1
这很好,但我似乎找不到有关为这些第三方应用添加文件附件的信息。 - TealShift

6

我对 @Mahmud Assan 的答案进行了升级和简化,以适应新的SwiftUI 生命周期

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

用法:

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")
        })

这很棒。你能解释一下你是怎么想到的吗?我觉得这会有帮助。如果不行,无论如何感谢! - Tank12
您现在对于 UIApplication.shared.windows 有以下警告:'windows' 在 iOS 15.0 中已被弃用:请改用相关窗口场景上的 UIWindowScene.windows。您有解决方案吗? - lorenzo

6
对于像我这样的人,想要更好的解决方案而不会干扰用户屏幕的人,我在这篇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>

接下来,您需要创建一个新的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)
    }
}

现在,前往您想要实施此操作的视图

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

2

我认为没有必要绑定isPresented或result,因此我的解决方案是在调用MFMailComposeViewControllerDelegate时使用回调。这也使得结果不可为空。

import Foundation
import MessageUI
import SwiftUI
import UIKit

public struct MailView: UIViewControllerRepresentable {
    public struct Attachment {
        public let data: Data
        public let mimeType: String
        public let filename: String

        public init(data: Data, mimeType: String, filename: String) {
            self.data = data
            self.mimeType = mimeType
            self.filename = filename
        }
    }

    public let onResult: ((Result<MFMailComposeResult, Error>) -> Void)

    public let subject: String?
    public let message: String?
    public let attachment: Attachment?

    public class Coordinator: NSObject, MFMailComposeViewControllerDelegate {
        public var onResult: ((Result<MFMailComposeResult, Error>) -> Void)

        init(onResult: @escaping ((Result<MFMailComposeResult, Error>) -> Void)) {
            self.onResult = onResult
        }

        public func mailComposeController(
            _ controller: MFMailComposeViewController,
            didFinishWith result: MFMailComposeResult,
            error: Error?
        ) {
            if let error = error {
                self.onResult(.failure(error))
            } else {
                self.onResult(.success(result))
            }
        }
    }

    public init(
        subject: String? = nil,
        message: String? = nil,
        attachment: MailView.Attachment? = nil,
        onResult: @escaping ((Result<MFMailComposeResult, Error>) -> Void)
    ) {
        self.subject = subject
        self.message = message
        self.attachment = attachment
        self.onResult = onResult
    }

    public func makeCoordinator() -> Coordinator {
        Coordinator(onResult: onResult)
    }

    public func makeUIViewController(
        context: UIViewControllerRepresentableContext<MailView>
    ) -> MFMailComposeViewController {
        let controller = MFMailComposeViewController()
        controller.mailComposeDelegate = context.coordinator
        if let subject = subject {
            controller.setSubject(subject)
        }
        if let message = message {
            controller.setMessageBody(message, isHTML: false)
        }
        if let attachment = attachment {
            controller.addAttachmentData(
                attachment.data,
                mimeType: attachment.mimeType,
                fileName: attachment.filename
            )
        }
        return controller
    }

    public func updateUIViewController(
        _ uiViewController: MFMailComposeViewController,
        context: UIViewControllerRepresentableContext<MailView>
    ) {
        // nothing to do here
    }
}

使用方法

struct ContentView: View {
    @State var showEmailComposer = false

    var body: some View {
        Button("Tap me") {
            showEmailComposer = true
        }
        .sheet(isPresented: $showEmailComposer) {
            MailView(
                subject: "Email subject",
                message: "Message",
                attachment: nil,
                onResult: { _ in
                     // Handle the result if needed.
                     self.showEmailComposer = false
                }
            )
        }
    }
}

2
在iOS 14之前,iOS上的默认邮件应用是Mail。当然,你也可以安装其他邮件应用程序。
   if MFMailComposeViewController.canSendMail() {
    let mailController = MFMailComposeViewController(rootViewController: self)
    mailController.setSubject("Test")
    mailController.setToRecipients(["mail@test.com"])
    mailController.mailComposeDelegate = self
    present(mailController, animated: true, completion: nil)
}

今天, 作为一名开发者,我希望尊重用户选择的电子邮件应用程序,无论是Mail、Edison、Gmail、Outlook还是Hey。为此,我不能使用MFMailComposeViewController。相反,我必须将mailto添加到Info.plist中的LSApplicationQueriesSchemes键中,然后当用户想要发送电子邮件时,使用以下代码:

if UIApplication.shared.canOpenURL(url) {
    UIApplication.shared.open(url, options: [.universalLinksOnly : false]) { (success) in
        // Handle success/failure
    }
}

与MFMailComposeViewController不同,这种方法将用户发送到他们选择的电子邮件应用程序,并同时关闭源应用程序。这并不理想。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接