How to prevent crash on Cancel of MFMailComposeViewController?
Asked Answered
T

2

5

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).

Tilden answered 20/7, 2012 at 18:1 Comment(6)
animating may have the same effect as all your extra dispatch code, also consider setting break points at the dispatch code and the switch cases before it to see where the crash happensPanfish
Crash log posted. I have to admit that I don't understand how a small delay fixes this. Yet.Legislatorial
Please see my last edit. This seems to be an Apple bug.Legislatorial
Editing old comment: ShareKit appears to have lots of memory leaks. They don't know the patterns for delegates and autoreleasing if you assign views to self.view style objects: specifically the TwitterForm and its creating-controllerBrother
Check this one: #4066427 Worked for us, after lots of searching. +1 to him!Brother
here I am 6 years later, struggling with the same issue. delaying the dispatch didn't fix it.Calciferous
D
6

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.

Dissert answered 21/7, 2012 at 1:2 Comment(5)
Not using ARC. Just tried your potential workaround. Still crashing. Given your explanation, why do you think that a delayed call to [self dissmissModalViewControllerAnimated:NO] band-aids the problem?Legislatorial
Perhaps the call to -actionSheet:didDismissWithButtonIndex: happens after the autorelease pool is drained (e.g. in the next run loop)? You can try retaining the MFMCVC for a little longer with [controller performSelector:@selector(class) withObject:nil afterDelay:1] or so.Dissert
OK, that worked. I also discovered that a simple [controller retain] just prior to [self dismissModalViewControllerAnimated:NO] will do the trick too. No leaks (checked with Instruments) under all possible dismiss modes for mail compose controller. I'll go-ahead and mark this as the correct answer because of your discussion of the sequence of events that takes place and the two additional work-around ideas (beyond a simple delay) that are contained in these comments. Bug reported to Apple. We'll see what comes back. Thanks.Legislatorial
Additional note: [[controller retain] autorelease] does not work. You still get a crash.Legislatorial
A "bare" [controller retain] is a leak. Leaks does not catch all leaks. (In particular, the VC is registered for notifications so will never be detected as a leak; I should file a bug at some point.)Dissert
A
2

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];
}

}

Ahouh answered 10/9, 2012 at 14:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.