I have an issue with MFMailComposeViewController when trying to implement from an NSObject class.
I have used MFMailComposeViewController numerous times without issue, but always within a View Controller.
However, in this case, I make a call to an NSObject Class which will run, whilst the user is normally on one ViewController. However, this code is run from a number of View Controllers, hence the use of the NSObject, and my need to allow the composer to present over ANY view controller.
The issue is that when complete, it needs to throw up an email composer, and I am unable to get this to work. Instead, it never launches the Mail Composer view.
I have the delegate in place, code follows, and the delegate is set within the .h.
MFMailComposeViewController *mc = [[MFMailComposeViewController alloc] init];
if([MFMailComposeViewController canSendMail]){
mc.mailComposeDelegate = self;
[mc setSubject:emailTitle];
[mc setMessageBody:messageBody isHTML:NO];
[mc setToRecipients:toRecipents];
[[mc navigationBar] setTintColor: [UIColor whiteColor]]; // Text color
// Present mail view controller on screen
[self presentViewController:mc animated:YES completion:^{
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
}];
}
- (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;
}
// Close the Mail Interface
[self dismissViewControllerAnimated:YES completion:NULL];
}
I have also tried to shift the code to AppDelegate, and use a notification from the NSObject class to fire the composer, however, using this method I find that the composer WILL display over a pushed VC, but if a modal view is currently displaying, it does NOT display the composer.
Finally, I tried adding an entirely separate view controller, adding the mail composer to it, and adding this via AppDelegate, but this too will only display over a pushed VC, and not a modal VC.
My thinking is that I need to find the 'top' of the navigation stack, in order to display the composer above this, but I am not 100% sure.
I would welcome comments from anyone who is able to throw any light on how I can achieve this, as I feel sure it must be possible, as I AM able to add the MailController to a modal view if the code is within the View Controller itself, but clearly this would mean I would need to add such said code to multiple VC's, which, whilst I could, seems to be an odd way to have to proceed.
Thanks all!
I thought I would come back and answer this!
The MFMailComposeViewController must be launched within a ViewController. This is by design.
Related
As in the title, I do not manage to have that callback called in order to dismiss the game center view controller neither on my iOS 7 iPhone nor iOS 8 iPad. This is the code I use:
GKGameCenterViewController *controller=nil;
- (IBAction)achievementButtonClicked:(id)sender {
if (!controller){
controller=[[GKGameCenterViewController alloc] init];
controller.delegate=self;
}
NSLog(#"controller=%#", controller);
if (controller) [self presentViewController:controller animated:YES completion:nil];
}
-(void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController{
[gameCenterViewController dismissViewControllerAnimated:YES completion:nil];
// I also tried [self dismissViewControllerAnimated:YES completion:nil] but anyway the function seems to not even enter here
}
If I take the function off, the delegate complaints it is missing, so the issue should not be connected to that. What might that be and how to fix it?
My problem is that I used:
controller.delegate=self;
omitting:
controller.gameCenterDelegate = self;
Once inserted the latter, the view controller dismisses without problems, both when I manually present the controller and when it is shown to login her. I really wonder why that beast has too delegates, if not to confuse developers...
I am able to successfully dismiss my MFMailComposeViewController in the didFinishWithResult delegate method. However, I have a scenario where I would like to dismiss the composer without user interaction, like selecting cancel or sending the mail.
I have looked in apple docs and was unable to find anything entirely useful. I have tried calling dismissViewControllerAnimated but that only seems to be working when I am inside the didFinishWithResult delegate method. Is there anyway to force that delegate method or dismiss the composer alternatively?
Assuming you are presenting your mail controller from a UIViewController, you may dismiss it programmatically by calling the UIViewController method:
dismissViewControllerAnimated:completion:
See this apple reference: dismissViewControllerAnimated:completion:
You did mention:
I have tried calling dismissViewControllerAnimated but that only seems
to be working when I am inside the didFinishWithResult delegate method
What you are experiencing may be indicative of a different problem as I was able to successfully do this outside of the mailComposeController:didFinishWithResult:error: delegate method.
Example:
-(void)showMail
{
MFMailComposeViewController *mailController = [[[MFMailComposeViewController alloc] init] autorelease];
//Set the message, subject, etc...
//Display
[someViewController presentViewController:mailController animated:YES completion:nil];
//As a proof of concept, close programmatically after a couple of seconds...
[self performSelector:#selector(dismissMailController) withObject:nil afterDelay:2.0];
}
-(void)dismissMailController
{
[someViewController dismissViewControllerAnimated:YES completion:nil];
}
I have a HomeView and a HomeDropDownView.
HomeDropDownView is shown as a drop-down view over the HomeView.
HomeView is a delegate of HomeDropDownView.
When I do an action in HomeDropDownView I want to call a delegate method in HomeView and have that delegate method present a third view controller, TestViewController from it's navigation controller.
If I try to launch TestViewController from anywhere in the class it works fine - except from the delegate method.
There are animations in HomeDropDownView but putting the call to the delegate method in the complition does not make the view controller appear. And in the case that I'm using this the animation's don't fire anyway; there's only a resizing without animation.
TestViewController's init does get called as well as the viewDidLoad but not the viewWillAppear and the view dose not appear.
Code:
HomeDropDownView
- (void)finalAction {
...
[self callDelegateAction];
...
- (void)calldelegateAction {
if ([self.delegate respondsToSelector:#selector(launchTestView)] ) {
[self.delegate launchTestView];
} else {
DLog(#"Error out to the user.");
}
}
HomeView
- (void)launchTestView {
//[self listSubviewsOfView:self.parentViewController.view];
NSLog(#"delegate method | self: %#", self);
TestViewController *tvc = [[TestViewController alloc] initWithNibName:#"TestViewController" bundle:nil];
//[self.navigationController presentViewController:tvc animated:YES completion:nil];
//[self.view.window.rootViewController presentViewController:tvc animated:YES completion:nil];
//[self.navigationController pushViewController:tvc animated:YES];
AppDelegate *appdelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appdelegate.tabBarController.navigationController presentViewController:tvc animated:YES completion:^() {
NSLog(#"Done!");
}];
}
None of the above approaches work. But if I put the exact same code into the viewDidAppear or put it in a button action method, it will work fine. At the time of calling the delegate method's self is HomeView and all the subviews, including the nav controller do seem to be there. This is in a tabcontroller-based project but I think that any of the above are acceptable ways to call the nav controller still.
What am I missing? Why does my delegate method not want to push/present a viewcontroller on HomeView's Nav controller? It's probably something I'm missing but I can't find a reason in the Apple Docs or any other thread.
Thanks for the help!
Sadly this turned out to be that HomeView was being changed underneath the execution of the message. So by the time the HomeView got the message call it was no longer the same HomeView object that had requested action in the first place. So it was not the same delegate.
This was done so that it would appear to the user that the same view was being used for different things.
But this is a good example of why you should not destroy and re-create critical views. We should have been using the same view and reloading the objects instead if we knew that we would be sending messages. Or had some notion of a control structure.
When I call dismissViewControllerAnimated:completion: to dismiss a UIViewController the completion block is never executed when the corresponding view is in the middle of being animated onto the screen (using presentViewController:animated:completion:).
The UIViewController does not even dissappear. It is like dismissViewControllerAnimated:completion: is being ignored.
The following code is a simplified code example because the original is much bigger. The code I have given below simulates a use-case where a network communication error might trigger a view to popup whilst another view is also being popped-up at the same time..
Code example:
NSLog(#"Presenting view");
[self presentViewController:changeLocationViewController animated:YES completion:^{
NSLog(#"View done presenting");
}];
NSLog(#"Dismissing view");
[self dismissViewControllerAnimated:NO completion:^{
NSLog(#"View done dismissing");
}];
Log output is:
2013-08-28 16:14:12.162 [1708:c07] Presenting view
2013-08-28 16:14:12.178 [1708:c07] Dismissing view
2013-08-28 16:14:12.583 [1708:c07] View done presenting
Does anyone know how to dismiss the UIViewController in these circumstances?
Thanks in advance.
The reason this code snippet isn't working is because the completion block in these methods are executed at a later time after the animations have completed. You can see this in your logs: "Dismissing view" happens before "View done presenting". Try this instead:
NSLog(#"Presenting view");
[self presentViewController:changeLocationViewController animated:YES completion:^{
NSLog(#"View done presenting");
NSLog(#"Dismissing view");
[self dismissViewControllerAnimated:NO completion:^{
NSLog(#"View done dismissing");
}];
}];
EDIT:
If you need to make sure the view is dismissed when the network error happens, try setting a boolean instance variable called networkErrorFound.
When you finish the network connection, set this to YES if an error happens. Then use this code:
[self presentViewController:changeLocationViewController animated:YES completion:^{
NSLog(#"View done presenting");
NSLog(#"Dismissing view");
if (self.networkErrorFound) {
[self dismissViewControllerAnimated:NO completion:^{
NSLog(#"View done dismissing");
}];
}
}];
That way, it'll wait until it's done presenting to dismiss. You would also need to handle the case that the error happens after the animation is done (for instance, a slow connection that eventually fails), but that's outside the scope of this question.
Why dont you dismiss it when its done loading?
[self presentViewController:changeLocationViewController animated:YES completion:^{
NSLog(#"View done presenting");
NSLog(#"Dismissing view");
[self dismissViewControllerAnimated:NO completion:^{
NSLog(#"View done dismissing");
}];
}];
OK. It seems like you want to present a VC, but if there is no network found, close the VC. The only reason that I can think of needing to do it this way is if the network only gets checked in the new VC that you are presenting (and want to dismiss if it fails to connect).
And you would be able to achieve that by implementing the code shown in the answer given by #aopsfan.
But think about that UI flow. You are telling a starving man (the user) he can have a sandwich (the next VC that he wants to see)... But WAIT! (dismiss the wanted VC) No, you can't have the sandwich (no network)! Fooled you!.
The way to do it to keep the UI flow nice and not aggravating, would be to check for network connection before presenting the VC. Probably check for network in the IBAction (?) that you use to present the new VC. That way, you can check before presenting. Instead of present-cancel
Heck, you could even show an HUD "in progress" View to let the user know what happening!
I have some social sharing code that looks like this:
SLComposeViewController *composer = [SLComposeViewController composeViewControllerForServiceType:…];
[composer setInitialText:…];
[composer addURL:…];
[composer setCompletionHandler:^(SLComposeViewControllerResult result) {
[someController dismissViewControllerAnimated:YES completion:^{
… // 1
}];
}];
[someController presentModalViewController:composer animated:YES];
The problem is that the code behaves differently for Facebook and Twitter. When the user confirms the Facebook compose screen, the composer apparently dismisses itself, because the completion handler marked as 1 is never called and even when I remove the dismissViewControllerAnimated: call, everything works fine.
On the other hand, when user confirms the Twitter compose screen and I don’t dismiss it by hand, the compose screen slides out, but the app stays stuck, like some controller is still in foreground. When I add the dismissViewControllerAnimated: call, the problem disappears and the completion handler (1) is called correctly.
Did you also notice this behaviour? Am I doing something wrong? This is current iOS 6, sample code on GitHub. I have reported the problem to Apple (Radar #12642889), no reaction yet.
I'm doing something similar in my app, and the only difference to your code is that I'm sending dismissModalViewControllerAnimated: to self instead of sending it to the view controller.Both facebook and twitter composer slide away.
This is my code:
SLComposeViewController *composer = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter];
[composer setInitialText:text];
[composer setCompletionHandler:^(SLComposeViewControllerResult result) {
...
[self dismissModalViewControllerAnimated:YES];
}];
[self presentModalViewController:composer animated:YES];
The issue was apparently fixed in iOS 7, tested on 7.0 beta build 11A4449d.
I have confirmed your issue with the behavior:
The Twitter version calls the completion handler that you set on the view controller, and expects that you will call dismissViewController from within this handler.
However the Facebook version calls dismissViewController itself before calling your completion handler. If you then call dismissViewController yourself, nothing happens, and you don't get any callback from any completion block that you might pass to dismissViewController.
If you leave out the dismissViewController call, then Twitter sharing gets stuck, but Facebook works.
Its a problem to create a solution if Apple is going to fix the behavior since your solution would then get broken. The main problem is that the behavior is not the same between the Weibo, Twitter and Facebook sharing versions of the same social sharing VC.
Here is how I fixed the problem:
SLComposeViewController *vc = [SLComposeViewController composeViewControllerForServiceType:serviceType];
if(vc==nil)
{
[self.delegate imageSaveDidSucceed:NO];
}
else
{
[vc addImage:self.image];
vc.completionHandler = ^(SLComposeViewControllerResult result) {
DEBUG_LOG(#"social sharing completed");
if(self.presentedViewController)
{
DEBUG_LOG(#"presented vc is not nil");
[self dismissViewControllerAnimated:YES completion:^{
DEBUG_LOG(#"dismissed vc and calling imageSaveDidSucceed");
[self.delegate imageSaveDidSucceed:YES];
}];
}
else
{
DEBUG_LOG(#"presented vc is nil");
[self.delegate imageSaveDidSucceed:YES];
}
};
[self presentViewController:vc animated:YES completion: ^{DEBUG_LOG(#"vc was presented");}];
}