Swift 5 - Email Class Helper / Manager

空扰寡人 提交于 2021-02-10 16:55:59

问题


Edit:

Big thanks to Paulw11 for helping me solve this issue. I've added the full code here for easy reuse:

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:

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.

I'm having issues making it work. Well, rather making it not crash if it doesn't work.

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:

  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. The problem I'm facing is that if canSendMail() is not true, then the class obviously cant initialize and crashes. 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. I've tried changing FeedbackManager from MFMailComposeViewController to a 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. So that I can check against canSendMail() before trying to initialize FeedbackManager(). 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.

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

回答1:


Subclassing MFMailComposeViewController is the wrong approach. This class is intended to be used "as-is". You can build a wrapper class if you like:

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:


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




回答2:


Solved this by first adding a class that checks if .canSendMail is true. If it is, it then taps into the postal sending class to present the MFMailComposeViewController.

This is the only workaround I've come up with that allows MFMailComposeViewController to be it's own MFMailComposeViewControllerDelegate. While also preventing a crash 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)
}



回答3:


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.

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

Hope it helps



来源:https://stackoverflow.com/questions/62617112/swift-5-email-class-helper-manager

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!