Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent crash on Cancel of MFMailComposeViewController?

Somewhere:

if([MFMailComposeViewController canSendMail])
{
    MFMailComposeViewController *email_vc = [[MFMailComposeViewController alloc] init];
    email_vc.mailComposeDelegate = self;

    [email_vc setSubject:subject];
    [email_vc setMessageBody:message isHTML:FALSE];
    [email_vc setToRecipients:recipients];

    [self presentModalViewController:email_vc animated:FALSE];
    [[UIApplication sharedApplication] setStatusBarHidden:TRUE];
    [email_vc release];
}
else
...

Somewhere else:

- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error
{
    switch (result) 
    {
        case MFMailComposeResultCancelled:
            NSLog(@"Cancelled");
            break;

        case MFMailComposeResultSaved:
            NSLog(@"Saved");
            break;

        case MFMailComposeResultSent:
            NSLog(@"Sent");
            break;

        case MFMailComposeResultFailed:
            NSLog(@"Compose result failed");
            break;

        default:
            NSLog(@"Default: Cancelled");
            break;
    }

    // This ugly thing is required because dismissModalViewControllerAnimated causes a crash
    // if called right away when "Cancel" is touched.

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.01 * NSEC_PER_SEC), dispatch_get_current_queue(), ^
    {
        [self dismissModalViewControllerAnimated:FALSE];
    }); 

}

That ugly "dispatch_after" block is the only way I can get this to work without a crash.

The context is that touching anything other than "Send" on the email compose view controller will cause a crash. Is there a way to deal with this without having to resort to this ugly band-aid? My theory on the band-aid is that an intermediate view is being presented when you touch "Cancel" to confirm that the user really wants to cancel. I am wondering if [self dismissModalViewControllerAnimated:FALSE]; is trying to dismiss a view out of sequence or something to that effect. By inserting a small delay I am theorizing that the mail compose view has time to cleanup before it is asked to go away.

I've seen a delay used in another question. The author did not go into any details though:

Crash On MFMailComposeViewController For iPad

EDIT 1: Adding crash log

Incident Identifier: ****************
CrashReporter Key:   *****************
Hardware Model:      iPhone4,1
Process:         ************* [9038]
Path:            /var/mobile/Applications/*********************
Identifier:      ***********************
Version:         ??? (???)
Code Type:       ARM (Native)
Parent Process:  launchd [1]

Date/Time:       2012-07-20 11:25:53.704 -0700
OS Version:      iPhone OS 5.0.1 (9A405)
Report Version:  104

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0xa003853a
Crashed Thread:  0

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libobjc.A.dylib                 0x316b9fbc 0x316b6000 + 16316
1   UIKit                           0x350caa9e 0x34f8e000 + 1297054
2   UIKit                           0x34fa6814 0x34f8e000 + 100372
3   UIKit                           0x34fabfb2 0x34f8e000 + 122802
4   QuartzCore                      0x33354ba0 0x33329000 + 179104
5   libdispatch.dylib               0x37896f74 0x37894000 + 12148
6   CoreFoundation                  0x37bac2d6 0x37b20000 + 574166
7   CoreFoundation                  0x37b2f4d6 0x37b20000 + 62678
8   CoreFoundation                  0x37b2f39e 0x37b20000 + 62366
9   GraphicsServices                0x376adfc6 0x376aa000 + 16326
10  UIKit                           0x34fbf73c 0x34f8e000 + 202556
11  *****************               0x00002346 main (main.m:14)
12  *****************               0x00002304 start + 32

EDIT 2: After much head scratching it appears that this is a genuine Apple bug.

I downloaded and ran the MailComposer sample project:

http://developer.apple.com/library/ios/#samplecode/MailComposer/Introduction/Intro.html

It works fine.

Then I edited the code to remove the animation while presenting and dismissing the mail composition controller.

[self presentModalViewController:picker animated:FALSE];

and

[self dismissModalViewControllerAnimated:FALSE];

Sure-enough, it crashed when "Cancel" was used to dismiss the email composition UI.

Running zombie brought this out:

-[MFMailComposeController actionSheet:didDismissWithButtonIndex:]: message sent to deallocated instance 0x7479ef0

I guess the action sheet gets the dismiss message instead of the mail compose view controller.

If someone could confirm behavior I'll report the bug.

EDIT 3: Bug reported.

The answer I accepted has a good explanation of the potential mechanism that is causing this issue. Also, during the back and forth in the answer comments two additional work-arounds were identified. All band-aids but now there are a few choices.

I haven't checked yet, but I suspect that ShareKit is subject to this bug as well (if the presentation of the mail compose view controller is not animated).

like image 666
martin's Avatar asked Jul 20 '12 18:07

martin's


2 Answers

I guess the action sheet gets the dismiss message instead of the mail compose view controller.

Not quite.

The sequence of events probably happens like this:

  • Action sheet calls -actionSheet:clickedButtonAtIndex: on its delegate (the MFMCVC).
    • MFMailComposeViewController calls -mailComposeController:didFinishWithResult:error: on its delegate (your VC)
      • Your VC calls [self dismissModalViewControllerAnimated:NO]
        • This causes the MFMCVC to be released. Since the dismiss isn't animated, there is no longer anything referring to the MFMCVC. It gets dealloced!
  • Action sheet calls -actionSheet:didDismissWithButtonIndex: on its delegate
    • But its delegate has been dealloced!
      • So it crashes!

The fix would be for Apple to do actionSheet.delegate = nil in -dealloc.

A potential workaround

[[self.modalViewController retain] autorelease]
[self dismissModalViewControllerAnimated:NO]

This is a bit trickier to do if you are using ARC.

like image 105
tc. Avatar answered Oct 12 '22 03:10

tc.


this works for me:

- (void) mailComposeController: (MFMailComposeViewController *) controller
       didFinishWithResult: (MFMailComposeResult) result
                     error: (NSError *) error {

if(result == MFMailComposeResultSent){
    [self dismissViewControllerAnimated:YES completion:NULL];
} else if (result == MFMailComposeResultCancelled) {
    [self dismissViewControllerAnimated:YES completion:NULL];
}

}

like image 38
boxi Avatar answered Oct 12 '22 05:10

boxi