Search code examples
swiftnsobjectnsobjectprotocol

An application crashes in case of working with MFMailComposeViewController


I am doing a screen that allows users to send mail. I wrote some code for it and it works fine:

import UIKit
import MessageUI

class ViewController: UIViewController, MFMailComposeViewControllerDelegate {

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        let mailComposer = MFMailComposeViewController()
        mailComposer.mailComposeDelegate = self
        let recipients = ["[email protected]"]
        mailComposer.setToRecipients(recipients)
        mailComposer.setSubject("Title")
        mailComposer.setMessageBody("Hello", isHTML: false)
        if MFMailComposeViewController.canSendMail() {
            self.present(mailComposer, animated: true, completion: nil)
        }
    }

    func mailComposeController(
        _ controller: MFMailComposeViewController,
        didFinishWith result: MFMailComposeResult,
        error: Error?
        ) {
        controller.dismiss(animated: true, completion: nil)
    }
}

Then I tried to move the functional of mail sending in another class named "MailSender". So it looks like:

import UIKit
import MessageUI

class MailSender: NSObject, MFMailComposeViewControllerDelegate {

    private var mailComposer = MFMailComposeViewController()

    init(email: String) {
        super.init()
        mailComposer.mailComposeDelegate = self
        let recipients = [email]
        mailComposer.setToRecipients(recipients)
        mailComposer.setSubject("Title")
        mailComposer.setMessageBody("Hello", isHTML: false)
    }

    func presentOverViewController(viewController: UIViewController) {
        if MFMailComposeViewController.canSendMail() {
            viewController.present(mailComposer, animated: true, completion: nil)
        }
    }

    func mailComposeController(
        _ controller: MFMailComposeViewController,
        didFinishWith result: MFMailComposeResult,
        error: Error?
        ) {
        controller.dismiss(animated: true, completion: nil)
    }
}

My view controller looks this way:

import UIKit
import MessageUI

class ViewController: UIViewController {

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        let mailSender = MailSender(email: "[email protected]")
        mailSender.presentOverViewController(viewController: self)
    }
}

In this case the screen opens sending mail view, but after cancel tap button the application crashes. What am I doing wrong?


Solution

  • The problem is probably that mailSender is a local that vanished before it has time to do anything:

    class ViewController: UIViewController {
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
            let mailSender = MailSender(email: "[email protected]")
            mailSender.presentOverViewController(viewController: self)
        }
    }
    

    Thus you end up with the delegate pointing at a nonexistent object — a simple recipe for a crash when the user cancels and the mail compose view controller tries to talk to that delegate. I would suggest making it an instance property:

    class ViewController: UIViewController {
        var mailSender : MailSender!
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
            self.mailSender = MailSender(email: "[email protected]") 
            self.mailSender.presentOverViewController(viewController: self)
        }
    }