I am experiencing a strange issue with the MFMailComposeViewController. I have been working with the MFMailComposeViewController in the past without any problems but now I can't figure out what's wrong this time and therefore I would like your help.
In my application I have multiple UIViewControllers where the one named MainViewController is a ViewController container where i implemented a left slide menu. In the MainViewController i added a UINavigationController as well. The UINavigationController is allocated and afterwards added to MainViewController like this:
[self addChildViewController:self.navigationController];
[self.centerView addSubview:self.navigationController.view];
[self.navigationController didMoveToParentViewController:self];
The above work perfectly fine and I can use the navigation controller to push and pop other UIViewControllers.
In the left menu I got different buttons, and one of them is named "About". When it is pressed the following code is executed:
NSArray *array = [NSArray arrayWithObject:[AboutViewController sharedViewController]];
[self.navigationController setViewControllers:array animated:NO];
In AboutViewController i got some buttons where one of them is "Send feedback". The "Send feedback" button executes the following code:
if([MFMailComposeViewController canSendMail]) {
MFMailComposeViewController *mailer = [[MFMailComposeViewController alloc] init];
mailer.mailComposeDelegate = self;
NSArray *toRecipients = [NSArray arrayWithObjects:[NSString stringWithFormat:#"feedback#test.com"], nil];
[mailer setToRecipients:toRecipients];
[mailer setSubject:[NSString stringWithFormat:#"Feedback"]];
[self presentViewController:mailer animated:YES completion:nil];
}
Which is also working. The AboutViewController implements the MFMailComposeViewControllerDelegate and its method:
- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error {
switch (result) {
case MFMailComposeResultCancelled:
//NSLog(#"Mail cancelled: you cancelled the operation and no email message was queued.");
break;
case MFMailComposeResultSaved:
//NSLog(#"Mail saved: you saved the email message in the drafts folder.");
break;
case MFMailComposeResultSent:
//NSLog(#"Mail send: the email message is queued in the outbox. It is ready to send.");
break;
case MFMailComposeResultFailed:
//NSLog(#"Mail failed: the email message was not saved or queued, possibly due to an error.");
break;
default:
//NSLog(#"Mail not sent.");
break;
}
// Remove the mail view
[self dismissViewControllerAnimated:YES completion:nil];
}
And its here I get the headache. I call the
[self dismissViewControllerAnimated:YES completion:nil];
as I did in previous apps where it worked without any problems. But in this case the MFMailComposeViewController is not being dismissed and the "Cancel" and "Send" buttons become inactive. I tried to implement a NSLog in the dismiss completion block but it is never being called.
I googled for hours now and can't find a way to solve this problem. I hope you can help me solve this strange issue. If you need more information just ask.
Thanks in advance.
- Sebastian
You have to open mail controller into parent viewcontroller. To do this you have to use delegate.
Call The MFMailComposeViewController in your parentclass and passed value by custom delegate
I managed to solve it. I found out that one of the threads that the MainViewController managed started an endless loop in main thread when the MainViewController should become visible.
Related
I am using MFMailComposeViewController in my application to compose a feedback E-Mail. The MFMailComposeViewController gets displayed, but can't be closed.
Method used to open the MFMailComposeViewController modal window:
-(IBAction) feedbackBtnClicked:(id)sender {
// Dismiss the Old View Controller
[self dismissViewControllerAnimated:NO completion:NULL];
// Present the New View Controller
if ([MFMailComposeViewController canSendMail])
{
MFMailComposeViewController *mail = [[MFMailComposeViewController alloc] init];
mail.mailComposeDelegate = self;
[mail setSubject:#"Sample Subject"];
[mail setMessageBody:#"Here is some main text in the email!" isHTML:NO];
[mail setToRecipients:#[#"example#mail.com"]];
[self presentViewController:mail animated:YES completion:NULL];
}
else
{
NSLog(#"This device cannot send email");
}
}
Here is what happens, when clicking on the buttons:
Senden (Send) - The E-Mail gets sent, but the modal window stays open; clicking on that button multiple times results in sending multiple E-Mails without the modal window ever getting closed.
Abbrechen (Cancel) - Nothing happens
How to dismiss make sure the MFMailComposeViewController gets dismissed after clicking on those buttons?
You need to implement the MFMailComposeViewControllerDelegate method mailComposeController:didFinishWithResult:error:, and dismiss the mail view controller…
- (void)messageComposeViewController:(MFMessageComposeViewController *)controller
didFinishWithResult:(MessageComposeResult)result
{
[self dismissViewControllerAnimated:YES completion:NULL];
}
I have a some kind of action sheet UIView which calls MFMailComposeViewController after tapping a button in it, the MFMailComposeViewController gets called and presented successfully, but it doesn't dismiss when the I tap Cancel and sometimes crashes, I know this is a known problem, but I have tried all the known fixes but it doesn't work for me. That's my code:
SendFeedback.h:
#import <UIKit/UIKit.h>
#import <MessageUI/MessageUI.h>
#interface SendFeedback : UIView <UITableViewDelegate, UITableViewDataSource, MFMailComposeViewControllerDelegate, UINavigationControllerDelegate>
//Useless code to the problem.
#property (nonatomic, retain) MFMailComposeViewController *feedBackComposer;
#end
SendFeedback.m:
- (void)baseInit
{
//Useless code to the problem.
feedBackComposer = [[MFMailComposeViewController alloc] init];
[feedBackComposer setMailComposeDelegate:self];
[feedBackComposer setToRecipients:[NSArray arrayWithObject:#"info#getibox.com"]];
[feedBackComposer setMessageBody:#"" isHTML:NO];
[feedBackComposer setSubject:#"What I Love About iBox"];
}
And to call the composer:
- (void)sendFeedbackComposer
{
[self.window.rootViewController presentViewController:feedBackComposer animated:YES completion:nil];
}
To dismiss it:
- (void)mailComposeController:(MFMailComposeViewController *)feedBackComposer didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
{
//This method doesn't get called.
[self.window.rootViewController dismissViewControllerAnimated:YES completion:nil];
NSLog(#"Error: %#", error);
}
EDIT: So, all the answers provided in this question were correct, and it was all my bad. What I wanted to do is: I had a UINavigationController which includes a UITableView one of the cells in that view should trigger an action sheet which is a UITableView by adding it as a subview, just like the previous one, this view also includes cells and one of them will do an action and it is calling an MFMailComposeViewController, which gets called but doesn't dismiss successfully even though I had its delegate set up and all the methods correct, the cause of the problem was the way of calling the MFMailComposeController, I first removed from super view that little UITableView then presented the MFMailComposeController, so when I dismissed the mail composer it crashed without even giving a result, which made me confuse the cause of the crash with the delegate method, fool me.
Here's the code:
- (void)removeView
{
if (self) {
[UIView animateWithDuration:0.3
animations:^{backgroundView.alpha = 0;}
completion:^(BOOL finished){
[UIView animateWithDuration:.3
animations:^{self.frame = CGRectMake(0, 568, 320, 568);}
completion:^(BOOL finished){[self removeFromSuperview];}];
}];
}
}
- (void)sendFeedbackComposer
{
feedBackComposer = [[MFMailComposeViewController alloc] init];
[feedBackComposer setMailComposeDelegate:self];
[feedBackComposer setToRecipients:[NSArray arrayWithObject:#"info#getibox.com"]];
[feedBackComposer setMessageBody:#"" isHTML:NO];
[feedBackComposer setSubject:#"What I Love About iBox"];
[self.window.rootViewController presentViewController:self.feedBackComposer animated:YES completion:nil];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
switch (indexPath.row) {
case 2: //That's the index of the MailComposer caller.
[self removeView];
[self sendFeedbackComposer];
break;
case 6:
[self removeView];
break;
default:
break;
}
}
- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
{
[feedBackComposer.presentingViewController dismissViewControllerAnimated:YES completion:nil];
NSLog(#"Error: %#", error);
}
Thanks to Stefan Atanasov, Jim, and Vitaly S.
Are you sure that delegate method isn't called? I made a test and it's called, but to dismiss mail composer use such code:
[feedBackComposer dismissViewControllerAnimated: YES completion: nil];
Also it's really strange to implement delegates in view implementation, not in view controller
To add something to Vitaly S.'s answer (due to lack of reputation I can't comment directly on it, sorry) - the delegate method is called, so the problem is the dismissal. You can try this:
[feedBackComposer.presentingViewController dismissViewControllerAnimated: YES completion: nil];
The delegate methods need to be called from the viewController, not from within the UIView.
Your view controller that calls the UIView will have your delegates
<UITableViewDelegate, UITableViewDataSource, MFMailComposeViewControllerDelegate, UINavigationControllerDelegate>
within it's header file.
You need to make a delegate method to pass information from the UIView to the controller, which will in turn call the mail and table delegate methods.
Here is a link to a question that demonstrates how to use a delegate protocol to trigger a method in the view controller from the UIView.
Trigger a method in UIViewController from its View
Hope this helps, Jim
you need to check is it device is configure to send mail by checking MFMailComposeViewController is avail to respond or not.
if([MFMailComposeViewController canSendMail]){
//your code
}
I'm calling a method from viewcontroller in an appdelegate. When I was testing functionality I just used NSLog message which works fine (so the connection between viewcontroller and appdelegate is OK). The problem appears once I add a email form into this method. The message I receive is:
Warning: Attempt to present <MFMailComposeViewController: 0x1fdc3990> on <ViewController: 0x1fd9e3b0> whose view is not in the window hierarchy!
Anyone who know what to do? I know there are lot of topics with 'whose view is not in the window hierarchy' issue but none of them helped me.
ViewController.m
...
-(void)mail{
NSLog(#"blablabla");
if ([MFMailComposeViewController canSendMail]) {
MFMailComposeViewController *mail = [[MFMailComposeViewController alloc] init];
mail.mailComposeDelegate = self;
[mail setSubject:#"Hello and welcome!"];
NSArray *toRecipients = [NSArray arrayWithObject:#"tomas.javnicky#gmail.com"];
[mail setToRecipients:toRecipients];
[mail setCcRecipients:toRecipients];
NSString *emailBody = #"Hey all!";
[mail setMessageBody:emailBody isHTML:NO];
mail.modalPresentationStyle = UIModalPresentationPageSheet;
[self presentViewController:mail animated:YES completion:nil];
} else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error!"
message:#"E-mail is not supported on your device"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
}
}
-(void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error{
switch (result) {
case MFMailComposeResultCancelled:
break;
case MFMailComposeResultSaved:
NSLog(#"mail saved");
break;
case MFMailComposeResultSent:
NSLog(#"mail sent");
break;
case MFMailComposeResultFailed:
NSLog(#"mail failed");
break;
}
[self dismissViewControllerAnimated:YES
completion:nil];
}
...
Appdelegate.m
...
-(void)something {
ViewController * vc = [[ViewController alloc] init];
[vc mail];
}
...
This is what solved my problem:
- (void)something {
ViewController *rootViewController = (ViewController*)self.window.rootViewController;
[rootViewController mail];
}
Also check the answer by rmaddy for more info.
Now that you've posted code, the problem is obvious. You create a view controller but you never display it (in the something method). Then you call a method on this undisplayed view controller (mail) which attempts to display the mail view controller from the undisplayed view controller. This is what causes the error.
You need to display the mail controller from a view controller that is already being displayed (such as the rootViewController maybe).
What is the point of your ViewController class?
I'm having trouble with removing a modal view.
I want to show (after pressing a button) a my own SendMailViewController which it self shows a MFMailComposeViewController. Then and after pressing cancel of send, in my own SendMailView controller in didFinishWithResult i do a [self dismissModalViewControllerAnimated:YES] and that works. The MFMailComposeView goes away.
But then the screen stays black....it think i also have to remove my SendMailViewController from it's parent. That's where i pushed the button...even after [self removeFromParentViewController] it still stays black...
Where do i go wrong?
And yes i would like the extra viewcontroller (SendMailViewController) because that controller will become the delegate of MFMailComposeViewController. Otherwise my caller (controller with the button) get's to much responsibility. Or do i also go wrong here?
Thanks,
/jr00n
- (IBAction)tapExportButton:(id)sender
{
SendMailViewController *sendMailController = [[SendMailViewController alloc]init];
[self presentViewController:sendMailController animated:YES completion:^() {[sendMailController openMailDialog];}];
[sendMailController release];
}
SendMailViewController:
- (void)openMailDialog
{
if ([MFMailComposeViewController canSendMail])
{
MFMailComposeViewController *mailer = [[MFMailComposeViewController alloc] init];
mailer.mailComposeDelegate = self;
...
[self presentModalViewController:mailer animated:YES];
}
}
- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error
{
....
// Remove the mail view
// first i did this:
// [self dismissModalViewControllerAnimated:YES];
[self dismissViewControllerAnimated:YES completion:^{[self removeFromParentViewController];}];
}
The problem is with the [self dismissViewControllerAnimated:YES completion:^{[self removeFromParentViewController];}]; in your didFinishWithResult method.
Remove that line and add the following line,
[controller dismissViewControllerAnimated:YES completion:^{[self dismissViewControllerAnimated:YES completion:nil]}];
That make sure we dismiss the controller after dismissing the MailController
Problem: My status bar appears on top of navigationBar after I present and dismiss MFMailComposerViewController as modal view.
-(IBAction)openMail:(id)sender
{
MFMailComposeViewController *mc = [[MFMailComposeViewController alloc] init];
mc.mailComposeDelegate = self;
[mc setSubject:emailTitle];
[mc setMessageBody:messageBody isHTML:YES];
[mc setToRecipients:toRecipents];
[mc.navigationItem.leftBarButtonItem setTintColor:[UIColor colorWithRed:144/255.0f green:5/255.0f blue:5/255.0f alpha:1.0f]];
[self presentViewController:mc animated:YES completion:NULL];
}
- (void) mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
{
switch (result)
{
case MFMailComposeResultCancelled:
NSLog(#"Mail cancelled");
break;
case MFMailComposeResultSaved:
NSLog(#"Mail saved");
break;
case MFMailComposeResultSent:
NSLog(#"Mail sent");
break;
case MFMailComposeResultFailed:
NSLog(#"Mail sent failure: %#", [error localizedDescription]);
break;
default:
break;
}
// Reset background image for navigation bars
[[UINavigationBar appearance] setBackgroundImage:[UIImage imageNamed:#"navigationBar.png"] forBarMetrics:UIBarMetricsDefault];
NSLog(#"%#",[GGStackPanel printFrameParams:self.view]);
// Close the Mail Interface
GGAppDelegate * appDel = [[UIApplication sharedApplication] delegate];
HHTabListController * contr = (HHTabListController*)appDel.viewController;
[contr setWantsFullScreenLayout:NO];
NSLog(#"%i",[contr wantsFullScreenLayout]);
[self dismissViewControllerAnimated:YES completion:NULL];
}
There are couple of similar questions on Stackoverflow, but none of the solutions suggested there work for me.
I have already tried:
status bar and Navigation bar problem after dismissed modal view
http://developer.appcelerator.com/question/120577/nav-bar-appears-underneath-status-bar
I tried presenting and dismissing from AppDelegate, no help.
Changing the view frame or navigationBar frame works, but I would have to do the same thing for all other views in my app (there are many of them). That will make my whole app depend on this tiny bug.
Screenshots
After dismissing MailComposer:
wantsFullScreenLayout is something complex and unrelated. ALL viewcontrollers are required to EITHER be embedded inside a "layout" viewcontroller (Apples UINavigationController, Apple's UITabBarController), OR to fully implement the complex logic of "how big should I be, and where am I positioned?" themselves.
Apple decided with iOS 1.0 that the main iPhone view you see DOES NOT START at 0,0. The Window that contains it starts at (0,0), but it is OVERLAPPED by the status bar.
I think this is a decision they came to regret, it made sense at the time, but in the long term it caused a lot of bugs.
The net effect is:
UINavigationController and UITabBarController have special (undocumented) internal code that makes it SEEM AS IF (0,0) is the top left corner - they force-resize/reposition any UIViewController's you add to them
...if you are NOT using one of them as your main controller, you have to re-implement that logic yourself. If you're using 3rd-party UIViewController instance, the logic is often implemented incorrectly or missing.
... you can FIX THIS yourself at runtime by re-positioning the UIViewController.view (its root view), e.g. by this:
(code)
UIViewController* rootController = // in this case HHTabController?
UIView* rootView = rootController.view;
CGRect frame = rootView.frame;
CGPoint oldOrigin = frame.origin;
CGPoint newOrigin = // calculate this, according to Apple docs.
// in your current case, it should be: CGPointMake( 0, 20 );
frame.origin = newOrigin;
frame.size = CGSizeMake( frame.size.width - (newOrigin.x - oldOrigin.x), frame.size.height - (newOrigin.y - oldOrigin.y) );
rootView.frame = frame;
...obviously, it's annoying having to do this everytime. This is why Apple strongly encourages everyone to use UINavigationController and/or UITabBarController :)
This alternative solution is also really handy to handle the weird behaviour of top layout guide repositioning itself after view controller is presented modally.
Add constraint for top spacing not to "Top Layout Guide", but to the actual "view". Select view (current distance ...) as shown in screenshot below:
Courtesy: https://stackoverflow.com/a/24818204/2959346