0

I have a VC that conforms to the MFMessageComposeViewControllerDelegate protocol.

I am successfully presenting this view controller with the following code:

- (IBAction)textAction:(id)sender {
    if(![MFMessageComposeViewController canSendText])
    {
        UIAlertView *warningAlert = [[UIAlertView alloc] initWithTitle:@"Error" message:@"Your device doesn't support SMS!" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
        [warningAlert show];
        return;
    }

    NSString *numberToCallOrText = self.phoneNumber;
    NSString *message = @"Test message";
    NSArray *recipients = [NSArray arrayWithObject:numberToCallOrText];
    MFMessageComposeViewController *messageController = [[MFMessageComposeViewController alloc] init];
    messageController.messageComposeDelegate = self;
    [messageController setRecipients:recipients];
    [messageController setBody:message];

    // Present message view controller on screen
    [self.view endEditing:YES];
    [self presentViewController:messageController animated:YES completion:nil];
}

Additionally, I am handling the finish result like so:

- (void)messageComposeViewController:(MFMessageComposeViewController *)controller didFinishWithResult:(MessageComposeResult) result
{
    switch (result) {
        case MessageComposeResultCancelled:
            NSLog(@"Canceled");
            break;

        case MessageComposeResultFailed:
        {
            NSLog(@"Failed");
            UIAlertView *warningAlert = [[UIAlertView alloc] initWithTitle:@"Error" message:@"Failed to send SMS!" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
            [warningAlert show];
            break;
        }

        case MessageComposeResultSent:
            NSLog(@"sent");
            [self.navigationController popViewControllerAnimated:YES];
            break;

        default:
            break;
    }
    [controller.view endEditing:YES];
    [self.navigationController popViewControllerAnimated:YES];
//    [self dismissViewControllerAnimated:YES completion:nil];
//    [controller popToViewController:self animated:YES];
//    [controller dismissViewControllerAnimated:YES completion:nil];
}

The three commented out lines are alternatives that I have tried. What's happening is that the MFMessageComposeViewController is remaining on the screen (though the keyboard is dismissed), but the delegate is being popped from the stack. Therefore, when I hit cancel again, I get a null reference error.

It's odd, because this same implementation works elsewhere in my code. The only difference is that I've set the body to be initialized.

Any ideas why the wrong VC is getting popped here?

Thanks.

Edit - The broken implementation is on a UITableViewController rather than UIView Controller... could that be what is causing the problem?

kb920
  • 3,039
  • 2
  • 33
  • 44
Jake T.
  • 4,308
  • 2
  • 20
  • 48
  • apart from the edit about the TableVC, everything was implemented (at one point) *exactly* the same way. Even commented out the `setBody` call. It still works elsewhere in the app. It's only right here that the cancel button won't work. – Jake T. Nov 02 '16 at 20:59

3 Answers3

0

You are presenting the message composer using presentViewController:animated:completion:. When a view controller is presented this way, it must be dismissed using dismissViewControllerAnimated:completion:.

But you are using popViewControllerAnimated: which is telling the navigation controller to dismiss whatever view controller is on the top of the navigation stack. This is not the message composer but the view controller that presented it.

You were close with one of the commented out lines. You need to replace:

[self.navigationController popViewControllerAnimated:YES];

with:

[controller dismissViewControllerAnimated:YES completion:nil];

You also need to remove the line:

[self.navigationController popViewControllerAnimated:YES];

when the message is sent. Don't do any extra dismissing.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • That is one of my commented out lines. The second one I tried, in fact, after `[self dismissViewControllerAnimated:YES completion:nil];`, which is what is working on the other implementation of this that I have. That, paired with `presentViewController`, is what Apple has in their documentation here: https://developer.apple.com/reference/messageui/mfmessagecomposeviewcontroller?language=objc – Jake T. Nov 02 '16 at 22:22
  • And it will work here too. You should also get rid of the call to `endEditing:` in the message composer delegate method. – rmaddy Nov 02 '16 at 22:24
  • It did not work here. I added `endEditing:` afterwards trying both `[self dismissViewController:..` and `[controller dismissViewController:...`, because I hadn't set the break point to see that the tableVC this is called from was getting deallocated, so I thought that the keyboard getting dismissed was somehow eating the dismiss call. – Jake T. Nov 02 '16 at 22:26
  • May help if I clarify, the behavior is that the keyboard gets dismissed (along with the text field for me to enter a message) when I first hit cancel, then I'm left with the text conversation and the cancel button in the corner. Hitting the cancel button the second time is what causes my crash, as the delegate no longer exists. – Jake T. Nov 02 '16 at 22:27
  • You are not being clear. What happens exactly when you use `[controller dismissViewControllerAnimated:YES completion:nil];`? – rmaddy Nov 02 '16 at 22:27
  • The same behavior as `[self dismissViewControllerAnimated:YES completion:nil];`. From other threads that I read, calling that method on `controller`, since it was presented modally, just passes the call back down to `self` anyway. – Jake T. Nov 02 '16 at 22:28
  • I appreciate the quick replies, and the help in general, btw. I hope I don't sound too combative to your suggestions. I feel like I'm shooting 'em all down and sound ungrateful. – Jake T. Nov 02 '16 at 22:29
  • Both `[self dismiss...]` and `[controller dismiss...]` seem to be deallocating the view that called 'textAction`, rather than the MFMessageComposeViewController. – Jake T. Nov 02 '16 at 22:31
  • See my updated answer (at the end). You have an extra call to `popViewController`. – rmaddy Nov 02 '16 at 22:35
  • Ahh, that is extraneous, but is not the issue here. I've removed it, but I actually hadn't been sending text messages. Using the cancel button. So, that block is never hit. The first block in the switch statement is hit, which does nothing except (not quite properly) dismisses the message VC. I'm working on pushing a blank VC that will be of a class that does nothing except present this MFMessageComposeVC. Hopefully that'll do the trick. Inspiration from this Reddit thread with the same issue: https://www.reddit.com/r/iOSProgramming/comments/34rcz5/does_anyone_have_experience_with_using_a/ – Jake T. Nov 02 '16 at 22:37
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/127231/discussion-between-jake-t-and-rmaddy). – Jake T. Nov 02 '16 at 22:40
0

Please see below code hope its useful for you....

-(void) showEmailModalView    
{
   if ([MFMailComposeViewController canSendMail])
    {
        MFMailComposeViewController* controller
        = [[MFMailComposeViewController alloc] init];
        controller.mailComposeDelegate = self;
        [controller setToRecipients: @[@"dept-em@np.edu.sg"]];

        [self presentViewController: controller
                           animated: YES
                         completion: nil];
    }
    else
    {
        UIAlertController* alert = [UIAlertController
           alertControllerWithTitle: @"Email not configured"
           message: @"Please add/enable an email "
           @"account in the phone to send email"
           preferredStyle: UIAlertControllerStyleAlert];

        UIAlertAction* ok = [UIAlertAction
           actionWithTitle: @"Ok"
           style: UIAlertActionStyleDefault
           handler: nil];

        [alert addAction: ok];

        [self presentViewController: alert
                           animated: YES
                         completion: nil];
    }
}


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

{ 

    switch (result)        
    {

      case MFMailComposeResultCancelled:
        break;
      case MFMailComposeResultSaved:
        break;
      case MFMailComposeResultSent:
        break;
      case MFMailComposeResultFailed:
        break;

      default:
      {

        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Email" message:@"Sending Failed - Unknown Error :-(" preferredStyle:UIAlertControllerStyleAlert];

        UIAlertAction* ok = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
        [alertController addAction:ok];
        [self presentViewController:alertController animated:YES completion:nil];


       }            
       break;
     }

     [self dismissViewControllerAnimated:controller completion:nil];
}
Ramkrishna Sharma
  • 6,961
  • 3
  • 42
  • 51
Shriram Kadam
  • 394
  • 1
  • 4
  • 20
  • Thanks, but that's more or less what I've already got. I believe the issue stems from the base VC I'm presenting modally from being a table view controller. I was able to circumvent the issue by first pushing a blank VC onto the stack that just opens up the MFMessageComposeVC and acts as its delegate, and pops itself when the message is finished. – Jake T. Nov 03 '16 at 16:18
0

The issue appears to stem from calling MFMessageComposeViewController from a UITableViewController instead of a UIViewController. Therefore, my solution was to instead have the table push another view controller whose sole purpose is pushing the MFMessageComposeViewController, and dismissing itself in the completion handler for the MFMessageComposeViewController.

Jake T.
  • 4,308
  • 2
  • 20
  • 48