Cannot dismiss MFMailComposeViewController that I called from SKScene - ios

I am trying to send an email from within my game app. In one of my SKScenes I have a sprite when you press it, it calls FeedbackVC().sendEmail(). This opens up the email viewController, but it does not dismiss properly. Here is my entire FeedbackVC class. I used the function getTopMostViewController because without it I was getting the error "Warning: Attempt to present on whose view is not in the window hierarchy!". My code will successfully open the MFMailComposeViewController with the prefilled fields and if I press the send button it actually will send to the email to my email, but it won't close and if I try to cancel the email it won't close either. Why won't my viewController close so it will continue back to my game after the email is sent or canceled?
import Foundation
import MessageUI
class FeedbackVC: UINavigationController, MFMailComposeViewControllerDelegate {
func getTopMostViewController() -> UIViewController? {
var topMostViewController = UIApplication.shared.keyWindow?.rootViewController
while let presentedViewController = topMostViewController?.presentedViewController {
topMostViewController = presentedViewController
}
return topMostViewController
}
func sendEmail() {
if MFMailComposeViewController.canSendMail() {
let mail = MFMailComposeViewController()
mail.mailComposeDelegate = self
mail.setToRecipients(["support#supportemail.com"])
mail.setSubject("In-App Feedback")
mail.setMessageBody("", isHTML: false)
self.getTopMostViewController()!.present(mail, animated: true, completion: nil)
} else {
print("Failed To Send Email!")
}
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: nil)
}
}
I have also tried setting the UINavigationControllerDelegate in the sendEmail() function.
mail.delegate = self as? UINavigationControllerDelegate
I have also tried things like popping the view controller and going back to the top most view controller in the mailComposeController.
popToRootViewContoller(animated: true)
getTopMostViewController()?.dismiss(animated: true, completion: nil)
I've tried following the guide on, https://developer.apple.com/documentation/messageui/mfmailcomposeviewcontroller, but it didn't work as I think my scenario is different since I am going from a SKScene to the MFMailCompose ViewController then back to a SKScene.

I'm one of the other developers working on this project. Posting in case someone has similar problems.
We were attempting to call our FeedbackVC in a way that looked like this:
if nodeTapped.name == "Feedback" {
let vc = FeedbackVC()
vc.emailButtonTapped(foo)
}
This would create the FeedbackVC class, call the emailButtonTapped method, and then deallocate the class from memory upon exiting the if statement. This means that clicking cancel or send would attempt to access the deallocated space, causing an EXC_BAD_ACCESS error. I fixed this by declaring vc as a class variable instead of declaring it inside the if statement.

Related

MFMailComposeViewController delegate not working on swift 4

I'm trying to dismiss the MFMailComposeViewController but the delegate is not triggered. It seems that it's a common issue and the answers are the same, but they do not work for me :(. I have a button that calls the function to send the mail. I first create a csv file, then the MFMailComposeViewController and attach the csv file to the mail. The mail is sent sometimes (the mail view controller does not dismiss after that) and the cancel button shows me the option to delete or save the draft but after that nothing happens.
Here's the code of the button:
import UIKit
import MessageUI
class UserInfoViewController: UIViewController, MFMailComposeViewControllerDelegate {
#IBAction func uploadPressed(_ sender: Any) {
let contentsOfFile = "Name,ID,Age,Sex,Time,\n\(name),\(id),\(age),\(sex),\(time)"
let data = contentsOfFile.data(using: String.Encoding.utf8, allowLossyConversion: false)
if let content = data {
print("NSData: \(content)")
}
func configuredMailComposeViewController() -> MFMailComposeViewController {
let emailController = MFMailComposeViewController()
//emailController.mailComposeDelegate = self as? MFMailComposeViewControllerDelegate
emailController.mailComposeDelegate = self
emailController.setToRecipients([""])
emailController.setSubject("CSV File")
emailController.setMessageBody("", isHTML: false)
emailController.addAttachmentData(data!, mimeType: "text/csv", fileName: "registro.csv")
return emailController
}
let emailViewController = configuredMailComposeViewController()
if MFMailComposeViewController.canSendMail() {
self.present(emailViewController, animated: true, completion: nil)
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
print("Delegate worked!")
controller.dismiss(animated: true, completion: nil)
}
}
}
Thank you very much in advance.
Your issue is being caused by putting the delegate method inside another method. You can't do that. Delegate functions need to be at the top level of the class. Simply move your mailComposeController(_:didFinishWith:error:) function out of the uploadPressed function.

MessageComposeViewController not calling delegate

I've been searching for solutions to my problem without any success...
The app I'm developing lets the user play a small quiz game and send the result as a text message. Everything works fine except when the MessageComposeViewController is suppose to dismiss (on send/cancel).
It seems like the MessageComposeViewController doesn't call the delegate since I don't get the print from the delegate function...
I have a separate class called SendMessage which handles the MessageComposeViewController, when the user click a button "Send" in a ViewController I create an instance of this class and present it.
Part of my ViewController with the send button:
#IBAction func Send(_ sender: Any) {
let sendResult = SendMessage()
if sendResult.canSend() {
let meddelande = sendResult.createMessage(result: 8, name: "Steve Jobs")
present(meddelande, animated: true, completion: nil)
} else {
alert.addAction(alertButton)
self.present(alert, animated: true, completion: nil)
}
}
The class which handles the MessageComposeViewController called SendMessage (I left some irrelevant code out)
func createMessage(result: Int, name: String) -> MFMessageComposeViewController {
let meddelande = MFMessageComposeViewController()
meddelande.messageComposeDelegate = self
meddelande.recipients = ["PhoneNumber"]
meddelande.body = name + ": " + String(result)
return meddelande
}
func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
print ("F*ck")
controller.dismiss(animated: true, completion: nil)
}
Grateful for any help!
I think you should hold a strong reference to it instead of a local variable
let sendResult = SendMessage()
declare it as instance variable
var sendResult:SendMessage?

How do I prevent a navigationController from returning to root when dismissing MFMailComposeViewController

When I dismiss an instance of MFMailComposeViewController or MFMessageComposeViewController that is presented modally from the third viewController in a navigation stack, the navigation stack is reset, and the root VC is reloaded. How can I prevent this behavior and remain on the original presenting viewController (third VC in the stack)? I get the same behavior whether I call dismiss from the presenting VC, the presented VC, or the navigationController.
This has been asked before, but I have not seen a solution.
App Structure looks like this:
TabBarController
Tab 1 - TripsNavController
-> Trips IntroductionVC (root VC) segue to:
-> TripsTableViewController segue to:
-> TripEditorContainerVC
- TripEditorVC (child of ContainerVC)
- HelpVC (child of ContainerVC)
Tab 2...
Tab 3...
Tab 4...
In the TripEditorVC I present the MFMailComposeViewController. The functions below are declared in an extension to UIViewController that adopts the MFMailComposeViewControllerDelegate protocol
func shareWithEmail(message: NSAttributedString) {
guard MFMailComposeViewController.canSendMail() else {
showServiceError(message: "Email Services are not available")
return
}
let composeVC = MFMailComposeViewController()
composeVC.setSubject("My Trip Plan")
composeVC.setMessageBody(getHTMLforAttributedString(attrStr: message), isHTML: true)
composeVC.mailComposeDelegate = self
present(composeVC, animated: true, completion: nil)
}
Then in the delegate method I dismiss the MFMailComposeVC:
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
switch result {
case .sent:
print("Mail sent")
case .saved:
print("Mail saved")
case .cancelled:
print("Mail cancelled")
case .failed:
print("Send mail failed")
}
if error != nil {
showServiceError(message: "Error: \(error!.localizedDescription)")
}
dismiss(animated: true, completion: nil)
}
I have tried the following to present and dismiss and get the same behavior, i.e.: the TripsNavController clears the nav stack and reloads the TripsIntroductionVC as its root VC:
self.present(composeVC, animated: true, completion: nil)
self.parent?.present(composeVC, animated: true, completion: nil)
self.parent?.navigationController?.present(composeVC, animated: true, completion: nil)
self.navigationController?.present(composeVC, animated: true, completion: nil)
You can also check with presentingViewController?.dismiss method to get the solution.
I have tried with following navigation stack.
I am able to send email successfully from Container VC's Send Email button using your code only.
Can you please check and verify navigation flow?
Please let me know if you still face any issue.
dismiss(animated: true, completion: nil)
to
self.dismiss(animated: true, completion: nil)
Try this
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let secondViewController = storyboard.instantiateViewControllerWithIdentifier("secondViewControllerId") as! SecondViewController
self.presentViewController(secondViewController, animated: true, completion: nil)
or
https://stackoverflow.com/a/37740006/8196100
Just Use Unwind Segue its Pretty simple and perfect in your case
I have used many times..
just look at this Unwind segue's example
. If u still don't able to find answer or not able to understand the Unwind segue then just reply to me.
Hope this will solve your problem.
I found the problem with my navigation stack today. I built a simple
project that duplicated the tabBarController/NavigationControler architecture of my problem project, component by component, until dismissing a MFMailComposeViewController caused my navigation stack to reset as described in my original post.
That immediately pointed to the source of the bug. In my subclassed UINavigationCotroller I was instantiating the root viewController in code so that I could skip an introductory view if the user had set a switch in the apps settings. In order to pick up changes in that switch setting I was calling my instantiation code in viewDidAppear of the navigationController. That worked fine EXCEPT when dismissing the mailComposeVC. The fix was to add a guard statement in viewDidAppear to return if the navControllers viewController collection was not empty, and send and respond to an NSNotification when the switch was changed.
class TopNavigationController: UINavigationController {
var sectionType: SectionType?
var defaults = UserDefaults.standard
var showIntroFlag: Bool = true
override func viewDidLoad() {
super.viewDidLoad()
// Handle initial load of the tab bar controller where we are not sent a sectionType
if sectionType == nil {
sectionType = .groups
}
setShowIntroFlag()
NotificationCenter.default.addObserver(self, selector: #selector(resetControllers), name: NSNotification.Name(rawValue: "kUserDidChangeShowIntros"), object: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
guard self.viewControllers.isEmpty else {
return
}
loadControllers()
}
func setShowIntroFlag() {
showIntroFlag = true
// Check NSUserDefaults to see if we should hide the Intro Views for all sections
if defaults.bool(forKey: "SHOW_SECTION_INTROS") == false {
showIntroFlag = false
}
}
func loadControllers() {
if showIntroFlag == true {
showIntro()
} else {
skipIntro()
}
}
func resetControllers() {
setShowIntroFlag()
loadControllers()
}

CNContactViewController Cancel Button Not Working

I'm trying to use the built-in new contact UI and am getting unexpected behavior with the cancel button. The code below works and calls up the new contact screen but the cancel button will only clear the screen entries not cancel out of the new contact screen. In the built in contacts app hitting cancel returns to the contact list screen. I would like the cancel button to close out the window.
#IBAction func newTwo(sender: AnyObject) {
AppDelegate.getAppDelegate().requestForAccess { (accessGranted) -> Void in
if accessGranted {
let npvc = CNContactViewController(forNewContact: nil)
npvc.delegate = self
self.navigationController?.pushViewController(npvc, animated: true)
}
}
}
did you implement CNContactViewControllerDelegate methods?
Here's a link to documentation
for example:
func contactViewController(viewController: CNContactViewController, didCompleteWithContact contact: CNContact?) {
self.dismissViewControllerAnimated(true, completion: nil)
}
It worked for me using the following code:
Swift 3
func contactViewController(_ vc: CNContactViewController, didCompleteWith con: CNContact?) {
vc.dismiss(animated: true)
}
Also I changed the way I was calling the controller:
Instead of:
self.navigationController?.pushViewController(contactViewController, animated: true)
the solution was:
self.present(UINavigationController(rootViewController: contactViewController), animated:true)
I found the solution using the example code Programming-iOS-Book-Examples written by Matt Neuburg:
Better way to do the dismissing would be to check if the contact is nil and then dismiss it. The dismiss doesn't work if you've pushed the view controller from a navigation controller. You might have to do the following:
func contactViewController(viewController: CNContactViewController, didCompleteWithContact contact: CNContact?) {
if let contactCreated = contact
{
}
else
{
_ = self.navigationController?.popViewController(animated: true)
}
}

Cannot remove Dismiss and SignUp Button from ParseUI

I can't see why my loginViewController continues to have the dismiss X and the Signup button. I don't want either. I thought by simply not including them in my logInController.fields array that they would not appear, but that does not seem to be the case. Any help?
let logInController = PFLogInViewController()
logInController.delegate = self
self.presentViewController(logInController, animated:true, completion: nil)
logInController.fields = [PFLogInFields.UsernameAndPassword, PFLogInFields.LogInButton, PFLogInFields.PasswordForgotten]
I am getting an error that might mean something.
2015-12-22 21:35:29.463 App Name[5737:99658] Warning: Attempt to present <PFLogInViewController: 0x7fd318c56020> on <app name.loginViewController: 0x7fd318d6c120> whose view is not in the window hierarchy!
I figured it out.
Make sure to import ParseUI
You must present your viewController in viewDidAppear not in viewDidLoad.
Here is the code that I am using, it builds and runs fine.
class LoginViewController: PFLogInViewController, PFLogInViewControllerDelegate {
override func viewDidAppear(animated: Bool) {
let logInController = PFLogInViewController()
logInController.delegate = self
logInController.fields = [PFLogInFields.UsernameAndPassword, PFLogInFields.LogInButton, PFLogInFields.PasswordForgotten, PFLogInFields.SignUpButton]
self.presentViewController(logInController, animated:true, completion: nil)
}

Resources