3

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)
Alpinista
  • 3,751
  • 5
  • 35
  • 46
  • 1
    You'll need to provide some actual code because there's no way we can know what you're doing wrong. Dismiss logic works based on who the caller is and if that caller has something presented. – Dima Jun 16 '17 at 19:24
  • Dima - See edited question with code. – Alpinista Jun 16 '17 at 21:29
  • Have you tried `presentingViewController?.dismiss(self, animated: true)`? – crizzis Jun 22 '17 at 15:41
  • For the presented MFMailComposeViewController, the presentingViewController evaluates to nil. For other presented viewControllers from the same parent, presentingViewController evaluates to the top TabBarController. When I dismiss the MFMailComposeViewController, the entire stack is reset to root. When any other presented viewController is dismissed, the stack is not reset. – Alpinista Jun 23 '17 at 19:27

4 Answers4

1

You can also check with presentingViewController?.dismiss method to get the solution.

I have tried with following navigation stack.

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.

Jayeshkumar Sojitra
  • 2,501
  • 4
  • 31
  • 44
  • After presenting MFMailComposeViewController from within the TripEditorVC, a check for the presentingViewController of the MFMailComposeViewController is nil. Other modally presented view controllers presented from the same TripEditoVC (UIAlertController, SLComposeViewController) report that the root TabBarController is their presentingViewController. When I dismiss the MFMailComposeViewController, the entire stack is reloaded. When I dismiss other presented viewControllers the stack is not reloaded, and the app remains on the TripEditorVC. – Alpinista Jun 23 '17 at 19:22
  • If you're using proper navigation stack then it won't be nil. Please check if your controller is not assigning from somewhere or it's not deallocating. – Jayeshkumar Sojitra Jun 27 '17 at 06:31
0

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

0

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.

Amit Kumar
  • 583
  • 5
  • 16
0

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()
}
Alpinista
  • 3,751
  • 5
  • 35
  • 46