简体   繁体   English

Swift 5 - Email Class 助手/管理器

[英]Swift 5 - Email Class Helper / Manager

Edit:编辑:

Big thanks to Paulw11 for helping me solve this issue.非常感谢 Paulw11 帮助我解决了这个问题。 I've added the full code here for easy reuse:我在这里添加了完整的代码以便于重用:

Class: Class:

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

In View Controller:在视图中 Controller:

Add Variable:添加变量:

var feedbackManager: FeedbackManager?

Use:利用:

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

I'm trying to make a class that handles initializing a MFMailComposeViewController to send an email inside of the app.我正在尝试制作一个 class 来处理初始化 MFMailComposeViewController 以在应用程序内部发送 email 。

I'm having issues making it work.我在使它工作时遇到问题。 Well, rather making it not crash if it doesn't work.好吧,如果它不起作用,就让它不会崩溃。

class: class:

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

And then calling it from a different view controller:然后从不同的角度调用它 controller:

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

The above works just fine if MFMailComposeViewController.canSendMail() == true.如果 MFMailComposeViewController.canSendMail() == true,上面的工作就很好。 The problem I'm facing is that if canSendMail() is not true, then the class obviously cant initialize and crashes.我面临的问题是,如果 canSendMail() 不正确,那么 class 显然无法初始化并崩溃。 Which makes sense.这是有道理的。

Error:错误:

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

I'm not sure where to go from here on how to get this working.我不确定从这里到 go 如何让它工作。 I've tried changing FeedbackManager from MFMailComposeViewController to a UIViewController.我尝试将 FeedbackManager 从 MFMailComposeViewController 更改为 UIViewController。 And that seems to work but because it's adding a view on the stack, it's causing a weird graphical display.这似乎可行,但因为它在堆栈上添加了一个视图,所以会导致奇怪的图形显示。

The other thing I could do is import MessageUI, and conform to MFMailComposeViewController for every controller I want to be able to send an email from.我可以做的另一件事是导入 MessageUI,并为每个 controller 遵守 MFMailComposeViewController,我希望能够从中发送 email。 So that I can check against canSendMail() before trying to initialize FeedbackManager().这样我就可以在尝试初始化 FeedbackManager() 之前检查 canSendMail()。 But that also doesn't seem like the best answer.但这似乎也不是最好的答案。

How else can I get this working?我还能如何让这个工作?

EDIT: I've gotten the code to work with this however, there is an ugly transition with the addition of the view onto the stack before it presents the MFMailComposeViewController.编辑:我已经获得了使用此代码的代码,但是在呈现 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)
    }
}

Subclassing MFMailComposeViewController is the wrong approach.子类化MFMailComposeViewController是错误的方法。 This class is intended to be used "as-is".此 class 旨在“按原样”使用。 You can build a wrapper class if you like:如果您愿意,可以构建一个包装器 class:

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

And then to use it from a view controller:然后从controller的角度来使用它:


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
}

You will need to hold a strong reference to the FeedbackManager in a property otherwise it will be released as soon as the containing function exits.您需要在属性中保持对FeedbackManager的强引用,否则一旦包含 function 退出,它将被释放。 My code above refers to a property我上面的代码是指一个属性

var feedbackManager: FeedbackManager?

While this will work, a better UX is if you check canSendMail directly and disable/hide the UI component that allows them to send feedback虽然这会起作用,但更好的 UX 是如果您直接检查canSendMail并禁用/隐藏允许他们发送反馈的 UI 组件

You can change the code as follows.您可以按如下方式更改代码。

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

Now, mailComposer can be opened as follows from another UIViewController class.现在,mailComposer 可以从另一个UIViewController class 打开如下。

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

Hope it helps希望能帮助到你

Solved this by first adding a class that checks if.canSendMail is true.通过首先添加一个检查 if.canSendMail 是否为真的 class 解决了这个问题。 If it is, it then taps into the postal sending class to present the MFMailComposeViewController.如果是,它然后利用邮政发送 class 来呈现 MFMailComposeViewController。

This is the only workaround I've come up with that allows MFMailComposeViewController to be it's own MFMailComposeViewControllerDelegate.这是我想出的唯一解决方法,它允许 MFMailComposeViewController 成为它自己的 MFMailComposeViewControllerDelegate。 While also preventing a crash if.canSendMail = false.同时还可以防止崩溃 if.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)
    }
}

Called with:调用:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM