Swift 5 - 电子邮件类助手/管理器

Posted

技术标签:

【中文标题】Swift 5 - 电子邮件类助手/管理器【英文标题】:Swift 5 - Email Class Helper / Manager 【发布时间】:2020-06-28 01:03:16 【问题描述】:

编辑:

非常感谢 Paulw11 帮助我解决了这个问题。我在这里添加了完整的代码以便于重用:

类:

import UIKit
import MessageUI

struct Feedback 
    let recipients: [String]
    let subject: String
    let body: String
    let footer: String


class FeedbackManager: NSObject, MFMailComposeViewControllerDelegate 

private var feedback: Feedback

private var completion: ((Result<MFMailComposeResult,Error>)->Void)?

override init() 
    fatalError("Use FeedbackManager(feedback:)")


init?(feedback: Feedback) 
    guard MFMailComposeViewController.canSendMail() else 
        return nil
    
    
    self.feedback = feedback


func send(on viewController: UIViewController, completion:(@escaping(Result<MFMailComposeResult,Error>)->Void)) 
    
    let mailVC = MFMailComposeViewController()
    self.completion = completion
    
    mailVC.mailComposeDelegate = self
    mailVC.setToRecipients(feedback.recipients)
    mailVC.setSubject(feedback.subject)
    mailVC.setMessageBody("<p>\(feedback.body)<br><br><br><br><br>\(feedback.footer)</p>", ishtml: true)
    
    viewController.present(mailVC, animated:true)


func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) 
    if let error = error 
        completion?(.failure(error))
        controller.dismiss(animated: true)
     else 
        completion?(.success(result))
        controller.dismiss(animated: true)
    


在视图控制器中:

添加变量:

var feedbackManager: FeedbackManager?

用途:

    let feedback = Feedback(recipients: "String", subject: "String", body: "Body", footer: "String")
    if let feedManager = FeedbackManager(feedback: feedback) 
        self.feedbackManager = feedManager
        self.feedbackManager?.send(on: self)  [weak self] result in
            switch result 
            case .failure(let error):
                print("error: ", error.localizedDescription)
            // Do something with the error
            case .success(let mailResult):
                print("Success")
                // Do something with the result
            
            self?.feedbackManager = nil
        
     else  // Cant Send Email: // Added UI Alert:
        let failedMenu = UIAlertController(title: "String", message: nil, preferredStyle: .alert)
        let okAlert = UIAlertAction(title: "String", style: .default)
        failedMenu.addAction(okAlert)
        present(failedMenu, animated: true)
    

我正在尝试创建一个类来处理初始化 MFMailComposeViewController 以在应用程序内部发送电子邮件。

我在使它工作时遇到了问题。好吧,如果它不起作用工作,不如让它不会崩溃。

类:

import UIKit
import MessageUI

struct Feedback 
    let recipients = "String"
    let subject: String
    let body: String


class FeedbackManager: MFMailComposeViewController, MFMailComposeViewControllerDelegate 
    
    func sendEmail(feedback: Feedback) 
        
        if MFMailComposeViewController.canSendMail() 
            
            self.mailComposeDelegate = self
            self.setToRecipients([feedback.recipients])
            self.setSubject("Feedback: \(feedback.subject)")
            self.setMessageBody("<p>\(feedback.body)</p>", isHTML: true)
            
         else 
            print("else:")
            mailFailed()
        
    
    
    func mailFailed() 
        print("mailFailed():")
        let failedMenu = UIAlertController(title: "Please Email Me!", message: nil, preferredStyle: .alert)
        let okAlert = UIAlertAction(title: "Ok!", style: .default)
        failedMenu.addAction(okAlert)
        self.present(failedMenu, animated: true, completion: nil)
    
    
    func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) 
        controller.dismiss(animated: true)
    

然后从不同的视图控制器调用它:

  let feedbackManager = FeedbackManager()
  feedbackManager.sendEmail(feedback: Feedback(subject: "String", body: "String"))
  self.present(feedbackManager, animated: true, completion: nil)
  tableView.deselectRow(at: indexPath, animated: true)

如果 MFMailComposeViewController.canSendMail() == true,上面的工作就很好。我面临的问题是,如果 canSendMail() 不正确,那么该类显然无法初始化并崩溃。这是有道理的。

错误:

Unable to initialize due to + [MFMailComposeViewController canSendMail] returns NO.

我不知道从这里开始如何让它发挥作用。我尝试将 FeedbackManager 从 MFMailComposeViewController 更改为 UIViewController。这似乎可行,但因为它在堆栈上添加了一个视图,所以会导致奇怪的图形显示。

我可以做的另一件事是导入 MessageUI,并为我希望能够从中发送电子邮件的每个控制器符合 MFMailComposeViewController。这样我就可以在尝试初始化 FeedbackManager() 之前检查 canSendMail()。但这似乎也不是最好的答案。

我还能如何让它工作?

编辑: 我已经得到了代码来处理这个问题,但是在它呈现 MFMailComposeViewController 之前,将视图添加到堆栈上会有一个丑陋的过渡。

class FeedbackManager: UIViewController, MFMailComposeViewControllerDelegate 
    
    func sendEmail(feedback: Feedback, presentingViewController: UIViewController) -> UIViewController 
        
        if MFMailComposeViewController.canSendMail() 
            
            let mail = MFMailComposeViewController()
            mail.mailComposeDelegate = self
            mail.setToRecipients([feedback.recipients])
            mail.setSubject("Feedback: \(feedback.subject)")
            mail.setMessageBody("<p>\(feedback.body)</p>", isHTML: true)
            
             present(mail, animated: true)
            return self
         else 
            print("else:")
            return mailFailed(presentingViewController: presentingViewController)
        
    
    
    func mailFailed(presentingViewController: UIViewController) -> UIViewController 
        print("mailFailed():")
        let failedMenu = UIAlertController(title: "Please Email Me!", message: nil, preferredStyle: .alert)
        let okAlert = UIAlertAction(title: "Ok!", style: .default)
        failedMenu.addAction(okAlert)
        return failedMenu
    
    
    func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) 
        controller.dismiss(animated: true)
        self.dismiss(animated: false)
    

【问题讨论】:

您没有为您的班级展示初始化程序,我认为这是消息来自的地方,但我建议您将其设为可失败的初始化程序并根据需要处理失败,或者你可以让sendEmail 返回false。无论哪种方式,试图从FeedbackManager内部发出警报都不是正确的方法 嘿保罗,我想我初始化了它?在不同的视图控件中使用 let feedbackManager = FeedbackManager()。您是正确的,在此类中显示警报是错误的方法。我已经尝试了各种方法,包括将 UIViewControllers 返回到呈现控制器。但无论我尝试什么,我都可以让一个工作正常。但不是两者兼而有之。 @Paulw11 - 重新阅读您的评论后,错误是由于该类是 MFMailComposeViewController 的子类而不是 UIViewController。我的问题是,如果它是 MFMailComposeViewController,则该类可以发送电子邮件。但是这样做,如果 canSendMail = false,它就会崩溃。 - 如果我改用 UIViewController,它会失败而不会崩溃,它会显示 MFMailComposeViewController 视图,但代理不会注册,因此发送/取消不会关闭视图。 你的问题是你在继承MFMailViewComposeViewController;你不应该继承那个类。它旨在按原样使用。 【参考方案1】:

子类化MFMailComposeViewController 是错误的方法。此类旨在“按原样”使用。如果你愿意,你可以构建一个包装类:

struct Feedback 
    let recipients = "String"
    let subject: String
    let body: String


class FeedbackManager: NSObject, MFMailComposeViewControllerDelegate 
    
    private var feedback: Feedback
    
    private var completion: ((Result<MFMailComposeResult,Error>)->Void)?
    
    override init() 
        fatalError("Use FeedbackManager(feedback:)")
    
    
    init?(feedback: Feedback) 
        guard MFMailComposeViewController.canSendMail() else 
            return nil
        
        
        self.feedback = feedback
    
    
    func send(on viewController: UIViewController, completion:(@escaping(Result<MFMailComposeResult,Error>)->Void)) 
        
        let mailVC = MFMailComposeViewController()
        self.completion = completion
        
        mailVC.mailComposeDelegate = self
        mailVC.setToRecipients([feedback.recipients])
        mailVC.setSubject("Feedback: \(feedback.subject)")
        mailVC.setMessageBody("<p>\(feedback.body)</p>", isHTML: true)
        
        viewController.present(mailVC, animated:true)
     
    
    func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) 
        if let error = error 
            completion?(.failure(error))
         else 
            completion?(.success(result))
        
    

然后从视图控制器中使用它:


let feedback = Feedback(subject: "String", body: "Body")
if let feedbackMgr = FeedbackManager(feedback: feedback) 
    self.feedbackManager = feedbackMgr
    feedback.send(on: self)  [weak self], result in 
        switch result 
            case .failure(let error):
                // Do something with the error
            case .success(let mailResult):
                // Do something with the result
        
        self.feedbackManager = nil
    
 else 
    // Can't send email

您需要在属性中保持对FeedbackManager 的强引用,否则它将在包含函数退出时立即释放。我上面的代码引用了一个属性

var feedbackManager: FeedbackManager?

虽然这可行,但更好的用户体验是直接检查 canSendMail 并禁用/隐藏允许他们发送反馈的 UI 组件

【讨论】:

嗨,保罗,谢谢您的回复。我试了一下。除了 mailComposeController 委托之外的所有东西似乎都在工作。 - 我不明白包装器是什么。 (实际上,今天早些时候才第一次读到它们。所以它在明天的学习清单上)但是代表为你工作吗? 我已经通过将打印语句放在每个方法的顶部进行了一些测试,并且似乎没有调用委托方法。 - 这是我一直遇到的同样问题。这就是为什么我在另一个答案中将 MFMailComposeViewController 子类化。因为设置 self.mailComposeDelegate = self 然后呈现 self 是我唯一能够弄清楚如何通过委托获得两个视图的方法。我不知道如何让委托函数正确触发。 嗨,保罗,感谢您的更新。我让它工作了!谢谢! :) 嗨,保罗,感谢您将这些放在一起。当我从 UIAlertViewController 调用代理 didFinish 时,它似乎对我不起作用,有什么想法吗?我也传递了al​​ertvc的父vc。【参考方案2】:

通过首先添加一个检查 .canSendMail 是否为真的类来解决这个问题。如果是,则它会进入邮政发送类以呈现 MFMailComposeViewController。

这是我想出的唯一解决方法,它允许 MFMailComposeViewController 成为它自己的 MFMailComposeViewControllerDelegate。如果 .canSendMail = false 也可以防止崩溃。

import UIKit
import MessageUI

struct Feedback 
    let recipients = ["Strings"]
    let subject: String
    let body: String


class FeedbackManager 

    func tryMail() -> Bool 
        if MFMailComposeViewController.canSendMail() 
            return true
         else 
            return false
        
    

    func mailFailed() -> UIViewController 
        let failedMenu = UIAlertController(title: "Please Email Me!", message: nil, preferredStyle: .alert)
        let okAlert = UIAlertAction(title: "Ok!", style: .default)
        failedMenu.addAction(okAlert)
        return failedMenu
    


class PostalManager: MFMailComposeViewController, MFMailComposeViewControllerDelegate 

    func sendEmail(feedback: Feedback) -> MFMailComposeViewController 
        if MFMailComposeViewController.canSendMail() 
            self.mailComposeDelegate = self
            self.setToRecipients(feedback.recipients)
            self.setSubject("Feedback: \(feedback.subject)")
            self.setMessageBody("<p>\(feedback.body)</p>", isHTML: true)
        
        return self
    

    func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) 
        controller.dismiss(animated: true)
    

调用:

let feedbackManager = FeedbackManager()
let feedback = Feedback(subject: "String", body: "Body")

switch feedbackManager.tryMail() 

case true:
let postalManager = PostalManager()
present(postalManager.sendEmail(feedback: feedback), animated: true)
case false:
present(feedbackManager.mailFailed(), animated: true)

【讨论】:

【参考方案3】:

您可以按如下方式更改代码。

struct Feedback 
    let recipients = "String"
    let subject: String
    let body: String


class FeedbackManager: NSObject, MFMailComposeViewControllerDelegate 
    
    func sendEmail(presentingViewController: UIViewController)) 
        
        if MFMailComposeViewController.canSendMail() 
            
            let mail = MFMailComposeViewController()
            mail.mailComposeDelegate = self
            mail.setToRecipients([feedback.recipients])
            mail.setSubject("Feedback: \(feedback.subject)")
            mail.setMessageBody("<p>\(feedback.body)</p>", isHTML: true)
            presentingViewController.present(mail, animated: true)
            
         else 
            print("else:")
            mailFailed(presentingViewController: presentingViewController)
        
    
    
    func mailFailed(presentingViewController: UIViewController) 
        print("mailFailed():")
        let failedMenu = UIAlertController(title: "Please Email Me!", message: nil, preferredStyle: .alert)
        let okAlert = UIAlertAction(title: "Ok!", style: .default)
        failedMenu.addAction(okAlert)
        presentingViewController.present(failedMenu, animated: true, completion: nil)
    
    
    func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) 
        controller.dismiss(animated: true)
    

现在,mailComposer 可以从另一个UIViewController 类打开,如下所示。

let feedbackManager = FeedbackManager()
feedbackManager.sendEmail(presentingViewController: self)

希望对你有帮助

【讨论】:

这就是我想要实现的目标。但是我遇到的问题是,如果按上述方式设置类,那么如果 canSendMail = false,它就会崩溃。但是将子类设置为 UIViewController 允许 UIAlertController 正确返回。并提出 MFMailComposeViewController。但是没有设置委托。所以虽然它会发送一封电子邮件,但视图不会关闭。 我在我的项目中使用了上面的代码。它可以在没有任何崩溃的情况下工作。对于解雇不起作用,请参阅反馈管理器实例实际上是一个实例变量而不是局部变量。 上面的确切代码对我不起作用。 - 我有它直接代码,它提出了一个错误。 不能在 Swift 中声明符合“NSObjectProtocol”; “FeedbackManager”应该继承“NSObject”。 - UIViewController 的子类化修复它。但是,如果 canSendMail = false 并且委托协议不起作用,它仍然会崩溃。

以上是关于Swift 5 - 电子邮件类助手/管理器的主要内容,如果未能解决你的问题,请参考以下文章

csharp 用C#编写的简单SMTP邮件客户端助手类,用于异步发送电子邮件。注意:使用Log4Net进行日志记录。

如何在控制器或视图之外使用 Zend Framework 部分视图助手?

扩展方法与助手类 [重复]

使用Laravel发送电子邮件错误“类Swift_Transport_Esmtp_AuthHandler不存在”

我无法使用 gmail SMTP 通过 swift 邮件发送邮件

Laravel 5.2 邮件发送中的 Swift_TransportException