Search code examples
iosswiftuitableviewhierarchymfmailcomposeviewcontroller

Page created by MFMailComposeViewController could not be shown because of hierarchy - Swift 4


I attempt to add a function, that is a mail page would pop up after the user touched a row in a table. Namely, it means that the user could activate a "function" (here the name of that function is "orderOfSendAnEmailToReportTheProblem") when the row is tapped. All of my codes were shown below. (This kind of code has been proposed by several genii on Stackoverflow...)

import Foundation
import UIKit
import MessageUI

class ReportProblem : UIViewController, MFMailComposeViewControllerDelegate {

func orderOfSendAnEmailToReportTheProblem() {
    let mailComposeViewController = configureMailController()
    if MFMailComposeViewController.canSendMail() {
    self.present(mailComposeViewController, animated: true, completion: nil)
    } else {
        showMailError()
    }
}
//Activate the series of the commands of sending the email.


func configureMailController() -> MFMailComposeViewController {
    let mailComposeVC = MFMailComposeViewController()
    mailComposeVC.mailComposeDelegate = self

    mailComposeVC.setToRecipients(["my email"])
    mailComposeVC.setSubject("Yo")

    return mailComposeVC
}
//Set the recipient and the title of this email automatically.

func showMailError() {
    let sendMailErrorAlert = UIAlertController(title: "Could not sned the email.", message: "Oops, something was wrong, please check your internet connection once again.", preferredStyle: .alert)
    let dismiss = UIAlertAction(title: "Ok", style: .default, handler: nil)
    sendMailErrorAlert.addAction(dismiss)
    self.present(sendMailErrorAlert, animated: true, completion: nil) //If you conform the protocol of NSObject instead of UIViewController, you could not finish this line successfully.
}
//Set a alert window so that it would remind the user when the device could not send the email successfully.

func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
    controller.dismiss(animated: true, completion: nil)
}
//Set this final step so that the device would go to the previous window when you finish sending the email.
}

However, a problem occurred. When I test it on my real device, and after I tapped that particular row, nothing happened, no any new page pop up... The Xcode only showed that "Warning: Attempt to present on whose view is not in the window hierarchy!" I have tried several ways, such as "view.bringSubview(toFront: mailComposeVC)" or adding the codes shown below at the end of my codes, but nothing worked.

func topMostController() -> UIViewController {
    var topController: UIViewController = UIApplication.shared.keyWindow!.rootViewController!
    while (topController.presentedViewController != nil) {
        topController = topController.presentedViewController!
    }
    return topController
}

I noticed that some other people also would face similar problems when they want to create the alert window, and the solution of that is to create an independent UIWindow, but I want to use mailComposeController to present the email page instead. Some others also faced some problems about MFMailComposeViewController, but their problems are not concerning to hierarchy. I was a novice of swift, and I was haunted by this problem for a whole day... I used swift 4 to develop my App, is anyone know how to solve this problem here?...


Solution

  • So now I'm writing another way to present which I'm using for generic views. Have Some code in another class for presentation of view so that you can reuse them throughout the app with these two methods.

    func slideInFromRight(parentView:UIView,childView:UIView) {
        childView.transform = CGAffineTransform(translationX: parentView.frame.maxX, y: 0)
        parentView.addSubview(childView)
        UIView.animate(withDuration: 0.25, animations: {
            childView.transform = CGAffineTransform(translationX: 0, y: 0)
        })
    }
    
    func slideOutToRight(view:UIView) {
        UIView.animate(withDuration: 0.25, animations: {
            view.transform = CGAffineTransform(translationX: view.frame.maxX, y: 0)
        },completion:{(completed:Bool) in
            view.removeFromSuperview()
        })
    }
    

    Now use these methods to present and remove custom view controller as follows

    let window = UIApplication.shared.keyWindow
    let vc = YourViewController().instantiate()
    self.addChildViewController(vc)
    let view = vc.view
    
    view.frame = CGRect(x: 0, y: 20, width: window!.frame.width, height: window!.frame.height-20)
    //Here Animation is my custom presenter class and shared is it's shared instance.
    
    Animation.shared.slideInFromRight(parentView: window!, childView: view)
    
    //Or you can use current View controller's view 
    Animation.shared.slideInFromRight(parentView: self.view!, childView: view)