Click alert button to show MFMailComposeViewController - swift

Trying to use MFMailComposeViewController when click alert button. But when I click alert button, controller doesn't pop-up. The code is below. I used extension and I'm trying to call sendmail function when clicking alert button.
extension Alert:MFMailComposeViewControllerDelegate {
func sendmail(){
let mailComposeViewController = configureMailController()
if MFMailComposeViewController.canSendMail() {
let VC = storyboard?.instantiateViewController(withIdentifier: "MainVC")
VC?.present(mailComposeViewController, animated: true, completion: nil)
} else {
showMailError()
}
}
func configureMailController() -> MFMailComposeViewController {
let mailComposerVC = MFMailComposeViewController()
let VC = storyboard?.instantiateViewController(withIdentifier: "MainVC")
mailComposerVC.mailComposeDelegate = VC as? MFMailComposeViewControllerDelegate
mailComposerVC.setToRecipients(["**"])
mailComposerVC.setSubject("**")
mailComposerVC.setMessageBody("\n\n\n\nModel: \nSistem versiyon: )\nuygulamaversiyon:", isHTML: false)
return mailComposerVC
}
func showMailError() {
let sendMailErrorAlert = UIAlertController(title: "Could not send email", message: "Your device could not send email", preferredStyle: .alert)
let dismiss = UIAlertAction(title: "Ok", style: .default, handler: nil)
sendMailErrorAlert.addAction(dismiss)
let VC = storyboard?.instantiateViewController(withIdentifier: "MainVC")
VC?.present(sendMailErrorAlert, animated: true, completion: nil)
}
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: nil)
}
}

Your code tries to present the mail view controller (and also the error alert) on a new view controller which you create with instantiateViewController. Since this new VC itself is not part of your app's VC hierarchy, neither it nor the mail VC will appear on screen.
To make this work, you can either pass a view controller which is part of your app into your sendmail method:
func sendmail(_ root: UIViewController) {
...
root.present(mailComposeViewController, animated: true, completion: nil)
...
Or you can use a globally accessible VC of your app; in a simple app using the root VC should work:
func sendmail() {
...
let root = UIApplication.shared.keyWindow?.rootViewController
root?.present(mailComposeViewController, animated: true, completion: nil)
...
I'd suggest the first approach because it is more flexible (e.g. it allows you to open your mail screen anywhere, even when your VC hierarchy gets more complex).

Related

having trouble trying to keep users logged in with addStateDidChangeListener()

So my goal for now is to successfully keep users logged in and show a certain viewController depending if they're logged in or not. I've read a lot of the Stack questions that showed up first on Google searches about this same topic and they said use addStateDidChangeListener() and that's exactly what I did.
I didn't know how to approach this, so I watched a Youtube video and copied the exact code the guy had, his project did what I wanted mine to do, so I gave it a shot. Unfortunately when I run the simulator, sign in, exit the simulator and simulate again, nothing changes. I will add my code and it's location.
This is the code in my AppDelegate.swift in the didFinishLaunchingWithOptions method
let storyboard = UIStoryboard.init(name: "Main", bundle: Bundle.main)
let auth = Auth.auth()
auth.addStateDidChangeListener { (_, user) in
switch user {
case nil:
guard self.activeViewController! is StudentSegmentedTableViewController else { return }
let nonLoggedInViewController = storyboard.instantiateViewController(withIdentifier: Constants.StoryboardIDs.GothereMainMenuStoryboardID) as! GothereMainMenuViewController
self.navigationController.setViewControllers([nonLoggedInViewController], animated: false)
self.navigationController.popToViewController(nonLoggedInViewController, animated: true)
self.activeViewController = nonLoggedInViewController
default:
guard self.activeViewController! is GothereMainMenuViewController else { return }
let alreadyLoggedInViewController = storyboard.instantiateViewController(withIdentifier: Constants.StoryboardIDs.StudentEventDashboardStoryboardID) as! StudentSegmentedTableViewController
self.navigationController.setViewControllers([alreadyLoggedInViewController], animated: false)
self.navigationController.popToViewController(alreadyLoggedInViewController, animated: true)
self.activeViewController = alreadyLoggedInViewController
}
}
let nonLoggedInViewController = storyboard.instantiateViewController(withIdentifier: Constants.StoryboardIDs.GothereMainMenuStoryboardID) as! GothereMainMenuViewController
let alreadyLoggedInViewController = storyboard.instantiateViewController(withIdentifier: Constants.StoryboardIDs.StudentEventDashboardStoryboardID) as! StudentSegmentedTableViewController
activeViewController = nonLoggedInViewController
switch Auth.auth().currentUser != nil {
case true:
activeViewController = alreadyLoggedInViewController
default:
break
}
navigationController = UINavigationController.init(rootViewController: activeViewController)
self.window?.rootViewController = navigationController
self.window?.makeKeyAndVisible()
I tried just this alone at first, and it didn't work so then I implemented a state listener in reasonable spots in my app.
First I added one that enables right after successful log in/signup and the segue is performed .
func enableAuth() {
authListener = Auth.auth().addStateDidChangeListener { (_, user) in
print("State Listener activated")
}
}
This is what I call in the viewDidLoad() of the segued viewController right after login/signup. To remove it, I simply call it when the logout button is pressed..
func disableAuthState() {
Auth.auth().removeStateDidChangeListener(self.authListener!)
print("State Listener Deactivated")
}
func studentLogoutSelected() {
var text = UITextField()
let alert = UIAlertController(title: "Logout", message: "Are you sure you want to logout?", preferredStyle: .alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (action) in
self.dismiss(animated: true, completion: nil)
}
let logoutAction = UIAlertAction(title: "Logout", style: .default) { (logoutAction) in
let firebaseAuth = Auth.auth()
do {
try firebaseAuth.signOut()
self.disableAuthState()
self.performSegue(withIdentifier: Constants.Segues.studentLogout, sender: self)
} catch let signOutError as NSError {
print("There was an error signing the user out. \(signOutError)")
}
}
alert.addAction(cancelAction)
alert.addAction(logoutAction)
present(alert, animated: true, completion: nil)
}
After all these functions and implementations, the shown blocks of code still don't do what I expected them to do. If anybody can point out issues or suggestions, that would be great, thanks.
First of all are you add FirebaseApp.configure() on your didFinishLaunchingWithOptions function in appdelegate? Then, Can you try call enableAuth in viewWillAppear()

segue/push to different view controller from IBAction function?

I am trying to push to the next viewcontroller programatically, such that I can save data in core data and then push to the next view controller. Basically when I tap a button with an IBAction, it will do all this. However, I am getting a thread 1 SIGABRT error whenever I use this code in my IBAction func:
#IBAction func onPlusTapped(){
let alert = UIAlertController(title: "New Course", message: nil, preferredStyle: .alert)
alert.addTextField { (textField) in
textField.placeholder = "Course"
}
let action = UIAlertAction(title: "New", style: .default){ (_) in
let course = alert.textFields!.first?.text!
print(course)
let coursy = Course(context: PersistenceService.context)
coursy.name = course
PersistenceService.saveContext()
}
alert.addAction(action)
present(alert, animated: true, completion: nil)
// jump to next view controller
segway()
}
func segway(){
let storyBoard: UIStoryboard = UIStoryboard(name: "viewcontroller", bundle: nil)
let balanceViewController = storyBoard.instantiateViewController(withIdentifier: "viewcontroller") as! ViewController
self.present(balanceViewController, animated: true, completion: nil)
}
}
If you want to jump to your next viewController once Course saved, you have to call the func segway() in the UIAlertAction completion block.
Example:
let action = UIAlertAction(title: "New", style: .default){ (_) in
let course = alert.textFields!.first?.text!
print(course)
let coursy = Course(context: PersistenceService.context)
coursy.name = course
PersistenceService.saveContext()
// jump to next view controller
segway()
}
Also double check your storyboard name and balanceViewController's identifier is "viewcontroller".
Why are you jumping segway() and present alert at the same time? shouldn't you put segway() inside your alert action?
Please see the following code.
#IBAction func onPlusTapped() {
let alert = UIAlertController(title: "New Course", message: nil, preferredStyle: .alert)
alert.addTextField { (textField) in
textField.placeholder = "Course"
}
let action = UIAlertAction(title: "New", style: .default){ (_) in
let course = alert.textFields!.first?.text!
print(course)
let coursy = Course(context: PersistenceService.context)
coursy.name = course
PersistenceService.saveContext()
// jump to next view controller
segway()
}
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
func segway() {
let storyBoard: UIStoryboard = UIStoryboard(name: "viewcontroller", bundle: nil)
let balanceViewController = storyBoard.instantiateViewController(withIdentifier: "viewcontroller") as! ViewController
self.present(balanceViewController, animated: true, completion: nil)
}
}
Hope this help!

Closing mail view controller upon clicking Cancel or Delete Draft

I created a Mail Controller in my application, it works perfectly, the sending part is fine as well. But when I click on "Cancel" or "Delete Draft" the window wont close and it basically gets stuck on the email screen.
I tried searching, all the fixes did not work. Here is my code.
#IBAction func btnEmail(_ sender: Any)
{
let mailCompose = MFMailComposeViewController()
mailCompose.mailComposeDelegate = self
mailCompose.setToRecipients(["issam.barakat#hct.ac.ae"])
mailCompose.setSubject("Amazing Health App!")
mailCompose.setMessageBody("This application is amazing, keep it up!", isHTML: false)
if MFMailComposeViewController.canSendMail()
{
self.present(mailCompose, animated: true, completion: nil)
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
// Dismiss the mail compose view controller.
controller.dismiss(animated: true, completion: nil)
}
First of all, we need to import the MessageUI module.
Second, we need to specify that the View Controller will conform to the MFMailComposeViewControllerDelegate protocol. Later, we’ll actually implement the method that this protocol outlines, which will allow us to make the email composer screen go away once the user is finished either sending an e-mail or cancels out of sending one.
try this..
class ViewController: UIViewController, MFMailComposeViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func sendEmailButtonTapped(sender: AnyObject) {
let mailComposeViewController = configuredMailComposeViewController()
if MFMailComposeViewController.canSendMail() {
self.present(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.delegate = self
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, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: nil)
}
}
reference https://www.andrewcbancroft.com/2014/08/25/send-email-in-app-using-mfmailcomposeviewcontroller-with-swift/
Just add MFMailComposeViewControllerDelegate to your class declaration. Example:
class ViewController: UIViewController, MFMailComposeViewControllerDelegate {
...
}

Sending an email from an app using MFMail

I'm trying to make my app able to access and send an email after a button is pressed. This is what I have so far and when I run it, and click the help button the alert pops up and the cancel button works fine but the email part of the alert crashes the app.
When it crashes it highlights the class AppDelegate: UIResponder, UIApplicationDelegate line and says Thread 1: signal SIGABRT.
#IBAction func helpButtonAction(sender: UIButton) {
let alert = UIAlertController(title: "Help", message: "Click 'Email' to email support", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Email", style: UIAlertActionStyle.Default, handler: { action in
let emailTitle = "Help Request"
let messageBody = ""
let toRecipents = ["sample#sample.com"]
let mc: MFMailComposeViewController = MFMailComposeViewController()
mc.mailComposeDelegate = self
mc.setSubject(emailTitle)
mc.setMessageBody(messageBody, isHTML: false)
mc.setToRecipients(toRecipents)
self.presentViewController(mc, animated: true, completion: nil)
}))
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) {
self.dismissViewControllerAnimated(false, completion: nil)
}
Heres the console error...
'NSInvalidArgumentException', reason: 'Application tried to present a nil modal view controller on target <app.ViewController: 0x13c64a5f0>.'
*** First throw call stack:
(0x183930f48 0x19847ff80 0x18923def4 0x189240800 0x188fbdea0 0x10004909c 0x1000491d0 0x100048cbc 0x100048d0c 0x1893297d8 0x189329f70 0x189218ba4 0x18921bd9c 0x188ff3668 0x188eff240 0x188efed3c 0x188efebc4 0x1886c5c2c 0x1013d5c68 0x1013db710 0x1838e81f8 0x1838e6060 0x183814ca0 0x18e87c088 0x188f2cffc 0x10004ee78 0x198cc28b8)
libc++abi.dylib: terminating with uncaught exception of type NSException
The problem is how you are instantiating a View Controller.
This code will instantiate the class, but not the view as you want to.
let mc: MFMailComposeViewController = MFMailComposeViewController()
The proper way to do so, can be found in the link above, i will just copy the code here.
You must set an identifier to your view controller, instantiating it by the storyboard, and then, presenting it.
let storyboard = UIStoryboard(name: "MyStoryboardName", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("someViewController") as! UIViewController
self.presentViewController(vc, animated: true, completion: nil)
Instantiate and Present a viewController in Swift

UIAlertController whose view is not in the window hierarchy

I'm trying to create an app with Swift and Parse. I'm using Xcode 7 and Swift 2.
I want to show a alert message when User is login failed, here is my function:
func logInViewController(logInController: PFLogInViewController, didFailToLogInWithError error: NSError?){
let alertLoginFailed = UIAlertController(title: "Login Failed", message: "Your username or password is invalid!", preferredStyle: UIAlertControllerStyle.Alert)
alertLoginFailed.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alertLoginFailed, animated: true, completion: nil)
print("Login failed.........!")
}
But I've got this error when run in the emulator:
2015-10-02 11:32:39.988 RedString[2089:886501] Warning: Attempt to present <UIAlertController: 0x7a934400> on <MyProject.ViewController: 0x7b985150> whose view is not in the window hierarchy!
Login failed.........!
I've googled about it, but I didn't found the clear solution.
Here is my whole class:
import UIKit
import Parse
import ParseUI
class ViewController: UIViewController, PFLogInViewControllerDelegate, PFSignUpViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
self.setupLoginView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func logInViewController(logInController: PFLogInViewController, didLogInUser user: PFUser){
self.dismissViewControllerAnimated(true, completion: nil)
}
func logInViewController(logInController: PFLogInViewController, didFailToLogInWithError error: NSError?){
let alertLoginFailed = UIAlertController(title: "Login Failed", message: "Your username or password is invalid!", preferredStyle: UIAlertControllerStyle.Alert)
alertLoginFailed.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alertLoginFailed, animated: true, completion: nil)
print("Login failed.........!")
}
func signUpViewController(signUpController: PFSignUpViewController, didSignUpUser user: PFUser){
self.dismissViewControllerAnimated(true, completion: nil)
}
func signUpViewController(signUpController: PFSignUpViewController, didFailToSignUpWithError error: NSError?){
print("Sign up failed.........!")
}
func setupLoginView(){
if(PFUser.currentUser() == nil){
let loginViewController = PFLogInViewController()
loginViewController.delegate = self
let signUpViewController = PFSignUpViewController()
signUpViewController.delegate = self
loginViewController.logInView!.logo = UIImageView(image: UIImage(named:"logo.png"))
loginViewController.signUpController = signUpViewController
self.presentViewController(loginViewController, animated: true, completion: nil)
}else{
print("login as: " + PFUser.currentUser()!.username!)
//prepare new view here
}
}
}
when
func logInViewController(logInController: PFLogInViewController, didFailToLogInWithError error: NSError?){
let alertLoginFailed = UIAlertController(title: "Login Failed", message: "Your username or password is invalid!", preferredStyle: UIAlertControllerStyle.Alert)
alertLoginFailed.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alertLoginFailed, animated: true, completion: nil)
print("Login failed.........!")
}
is called, your view controller is not on screen. Therefore you could either
dismiss the logInViewController, then display the alert and pass
setUpLogInView as the handler.
or in the above function, try logInController.presentViewController
instead of self.presentViewController
You have to call presentViewController:animated:completion on your login view controller.
Here is a simplified version of your class to show you what I mean:
class ViewController: UIViewController {
weak var loginViewController: UIViewController?
func setupLoginViewController() {
let loginVC = PFLogInViewController()
// setup loginVC
loginViewController = loginVC // store the reference
}
func loginDidFail() {
let alertVC = UIAlertController(...)
// setup alertVC
loginViewController?.presentViewController(...) // present the alert from the login view controller
}
}
I had the same problem in Objective-C, it occurs when the parent window has not yet been drawn to the screen. I solved the problem by calling the code from within viewDidAppear rather than viewDidLoad. See UIAlert error, whose view is not in the window hierarchy