I've been working on this app. I can't get the view of the email to close. What do I do?
import UIKit
import MessageUI
class ViewController: UIViewController, MFMailComposeViewControllerDelegate {
#IBOutlet weak var Label: UITextField!
#IBAction func SendOrder(_ sender: Any) {
let recipients = ["EmailHere"]
let title = "My order is:"
let message = Label.text
let mc: MFMailComposeViewController = MFMailComposeViewController()
mc.mailComposeDelegate = self
mc.setToRecipients(recipients)
mc.setSubject(title)
mc.setMessageBody(message!, isHTML: false)
self.present(mc, animated: true, completion: nil)
I have tried the Delegate method but it comes up with this:
What do I do??
https://i.stack.imgur.com/xqgTW.png
TRY THiS
#IBAction func btnEmail(_ sender: AnyObject) {
self.openEmail()
}
func openEmail() {
if MFMailComposeViewController.canSendMail() {
let mail = MFMailComposeViewController()
mail.mailComposeDelegate = self
mail.setSubject("Your subject Name")
mail.setMessageBody("Contact Us", isHTML: false)
mail.setToRecipients(["test#gmail.com"])
present(mail, animated: true, completion: { _ in })
}
else {
print("This device cannot send email")
}
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
switch result {
case .sent:
print("You sent the email.")
break
case .saved:
print("You saved a draft of this email")
break
case .cancelled:
print("You cancelled sending this email.")
break
case .failed:
print("Mail failed: An error occurred when trying to compose this email")
break
}
dismiss(animated: true, completion: { _ in })
}
Related
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)
}
}
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
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)
}
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
I have small problem. I'm using MFMailComposeViewController to send email after work. So I have var allRuns, and I've printed that var in few places, and it always show value = 3. But in my MailViewController into MessageBody it's equal 0/nil (I've set 0). If it is important- I'm taking var from Firebase, but earlier in the app. Where should I seek problem? That's some code below.
MailViewController:
import UIKit
import MessageUI
class MailViewController: UIViewController, MFMailComposeViewControllerDelegate {
let av = ActualValues()
override func viewDidLoad() {
super.viewDidLoad()
}
//MARK: - Buttons
#IBAction func emailButtonTapped(_ sender: UIButton) {
showMailComposer()
}
#IBAction func backButtonTapped(_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}
//MARK: -Func
func showMailComposer() {
if MFMailComposeViewController.canSendMail() {
let mailComposer = MFMailComposeViewController()
mailComposer.mailComposeDelegate = self
mailComposer.setToRecipients(["paereson#gmail.com"])
mailComposer.setSubject("Work of \(av.currentDate())")
mailComposer.setMessageBody("All runs: \(av.allRuns)", isHTML: false)
present(mailComposer, animated: true, completion: nil)
}
}
func mailComposeController(_ controller: MFMailComposeViewController,
didFinishWith result: MFMailComposeResult, error: Error?) {
if let _ = error {
//show alert
controller.dismiss(animated: true, completion: nil)
return
}
switch result {
case .cancelled:
print("Cancelled")
case .failed:
print("Failed")
case .saved:
print("saved")
case .sent:
print("Email sent")
#unknown default:
print("default")
}
controller.dismiss(animated: true, completion: nil)
}
}
Few lines in av = ActualValuse:
var allRuns: Int = 0
var runs: Array? = []
//All runs
func allRunsFunc() {
let ref1 = ref.child("\(currentYear())/\(currentMonth())/\(currentDay())")
ref1.observeSingleEvent(of: .value) { (snapshot) in
if let snapshots = snapshot.children.allObjects as? [DataSnapshot] {
self.allRuns = snapshots.count as Int
}
}
}
EDIT:
Should I use segue before MFMAilComposeViewController? If yes, how should I configure it?
import UIKit
import MessageUI
class MailViewController: UIViewController, MFMailComposeViewControllerDelegate {
let av = ActualValues()
override func viewDidLoad() {
super.viewDidLoad()
}
//MARK: - Buttons
#IBAction func emailButtonTapped(_ sender: UIButton) {
showMailComposer()
}
#IBAction func backButtonTapped(_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}
//MARK: -Func
func showMailComposer() {
if MFMailComposeViewController.canSendMail() {
let mailComposer = MFMailComposeViewController()
mailComposer.mailComposeDelegate = self
mailComposer.setToRecipients(["paereson#gmail.com"])
mailComposer.setSubject("Work of \(av.currentDate())")
mailComposer.setMessageBody("\(av.exportText())", isHTML: true)
present(mailComposer, animated: true, completion: nil)
}
}
func mailComposeController(_ controller: MFMailComposeViewController,
didFinishWith result: MFMailComposeResult, error: Error?) {
if let _ = error {
//show alert
controller.dismiss(animated: true, completion: nil)
return
}
switch result {
case .cancelled:
print("Cancelled")
case .failed:
print("Failed")
case .saved:
print("saved")
case .sent:
print("Email sent")
#unknown default:
print("default")
}
controller.dismiss(animated: true, completion: nil)
}
func configureMail(model: ActualValues) {
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.destination is MFMailComposeViewController {
let vc = segue.destination as? MFMailComposeViewController
vc?.configureMail(model: av)
}
}
}
You seem to be missing the execution of allRunsFunc function. Maybe call it in the ActualValue init method.
Below is the function for the native mail app open , Nothing happed when cancel button clicked and Send button on native mail app is sending the mail but user also got blocked.User didn't get any action respone.
#IBAction func openNativeEmail(_ sender: AnyObject){
if MFMailComposeViewController.canSendMail() {
debugPrint("can send mail")
let mailVC = MFMailComposeViewController()
mailVC.mailComposeDelegate = self
mailVC.setToRecipients(["pawanline#gmail.com"])
mailVC.setSubject("Testing mail App features")
mailVC.setCcRecipients(["pawan.kumar#iic.ac.in"])
mailVC.setMessageBody("Hi,just testing ", isHTML: false)
present(mailVC, animated: true, completion: nil)
} else {
print("Unable to send the mail")
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: nil)
}
You missing a closing bracket:
#IBAction func openNativeEmail(_ sender: AnyObject){
if MFMailComposeViewController.canSendMail() {
debugPrint("can send mail")
let mailVC = MFMailComposeViewController()
mailVC.mailComposeDelegate = self
mailVC.setToRecipients(["pawanline#gmail.com"])
mailVC.setSubject("Testing mail App features")
mailVC.setCcRecipients(["pawan.kumar#iic.ac.in"])
mailVC.setMessageBody("Hi,just testing ", isHTML: false)
present(mailVC, animated: true, completion: nil)
} else {
print("Unable to send the mail")
}
} // <----- THIS WAS MISSING
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: nil)
}
I'm using the Swift book to try to learn to code. I added the delegate method to dismiss the view, but it's not working. What am I missing here?
#IBAction func emailButtonTapped(_ sender: UIButton) {
if !MFMailComposeViewController.canSendMail() {
print("Can not send mail")
return
}
guard MFMailComposeViewController.canSendMail() else { return }
let mailComposer = MFMailComposeViewController()
mailComposer.mailComposeDelegate = self
mailComposer.setToRecipients(["example#example.com"])
mailComposer.setSubject("Look at this")
mailComposer.setMessageBody("Hello, this is an email from the app I made.", isHTML: false)
present(mailComposer, animated: true, completion: nil)
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
dismiss(animated: true, completion: nil)
}
}
You just need to declare the delegate function outside of the IBAction:
#IBAction func emailButtonTapped(_ sender: UIButton) {
if !MFMailComposeViewController.canSendMail() {
print("Can not send mail")
return
}
guard MFMailComposeViewController.canSendMail() else { return }
let mailComposer = MFMailComposeViewController()
mailComposer.mailComposeDelegate = self
mailComposer.setToRecipients(["example#example.com"])
mailComposer.setSubject("Look at this")
mailComposer.setMessageBody("Hello, this is an email from the app I made.", isHTML: false)
present(mailComposer, animated: true, completion: nil)
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
dismiss(animated: true, completion: nil)
}
I have a button in my app which opens up an email to be sent to me, when this button is pressed the email app on iPhone opens up and when sent is pressed the email is sent however the window doesn't close and then return to my app. Also when i press cancel it gives the option to save/delete draft but again doesn't close the window and return to my app. I have attached the email code below.
#IBAction func SendMessage(sender: AnyObject) {
var mail: MFMailComposeViewController!
let toRecipients = ["usalim76#gmail.com"]
let subject = "Enquiry"
let body = "Your body text"
mail = MFMailComposeViewController()
mail.mailComposeDelegate = self
mail.setToRecipients(toRecipients)
mail.setSubject(subject)
mail.setMessageBody(body, isHTML: true)
presentViewController(mail, animated: true, completion: nil)
func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) {
controller.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
}
}
looks like you forgot to implement the MFMailComposeViewControllerDelegate, add this:
func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) {
controller.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
}
Managed to fix it!
#IBAction func SendMessage(sender: AnyObject) {
let mailComposeViewController = configuredMailComposeViewController()
if MFMailComposeViewController.canSendMail() {
self.presentViewController(mailComposeViewController, animated: true, completion: nil)
} else {
self.showSendMailErrorAlert()
}
}
func configuredMailComposeViewController() -> MFMailComposeViewController {
let mailComposerVC = MFMailComposeViewController()
mailComposerVC.mailComposeDelegate = self // Extremely important to set the --mailComposeDelegate-- property, NOT the --delegate-- property
mailComposerVC.setToRecipients(["someone#somewhere.com"])
mailComposerVC.setSubject("Sending you an in-app e-mail...")
mailComposerVC.setMessageBody("Sending e-mail in-app is not so bad!", isHTML: false)
return mailComposerVC
}
func showSendMailErrorAlert() {
let sendMailErrorAlert = UIAlertView(title: "Could Not Send Email", message: "Your device could not send e-mail. Please check e-mail configuration and try again.", delegate: self, cancelButtonTitle: "OK")
sendMailErrorAlert.show()
}
// MARK: MFMailComposeViewControllerDelegate Method
func mailComposeController(controller: MFMailComposeViewController!, didFinishWithResult result: MFMailComposeResult, error: NSError!) {
controller.dismissViewControllerAnimated(true, completion: nil)
}
}