SLComposeViewController dismisses differently for Facebook and Twitter? - ios

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

Related

dismissViewControllerAnimated not working in iOS 9

I have a UIViewController subclass called NewsViewController which has a completion block property that is called from a button action. The controller is set up and presented in another view controller like this:
newsViewController.completionBlock = ^{
[self dismissViewControllerAnimated:YES completion:nil];
};
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:newsViewController];
[self presentViewController:navigationController animated:YES completion:nil];
In iOS 10 this all works fine, however in iOS 9 the view is not being dismissed. If I put a breakpoint there it does get hit.
I have tried the following without success:
Called it from the main thread (both synchronously and asynchronously)
I have tried it using GCD like this:
dispatch_async(dispatch_get_main_queue(), ^{
[self dismissViewControllerAnimated:YES completion:nil];
});
I have also tried it by putting the dismissal call into a method and then calling
[self performSelectorOnMainThread:#selector(dismissModalView) withObject:nil waitUntilDone:NO];
I don't actually thing the issue is the thread since a call to [NSThread isMainThread] from within the completion block returns YES.
Calling it with a delay
[self performSelector:#selector(dismissModalView) withObject:nil afterDelay:0.1];
Calling dismiss on another view controller
I have tried calling it on navigationController, self.presentedViewController and self.presentingViewController.
Calling dismiss directly from NewsViewController
In the button action where the completion block was called I called [self dismissViewControllerAnimated:YES completion:nil] directly.
Btw. just for fun I tried calling the dismiss method from the completion block of the presentViewController method and there it did get dismissed.
I have finally located the problem and it was quite unexpected. The thing is that the NewsViewController is presented over my login view controller. This controller allows the user to use Touch ID to log in so it requests the Touch ID prompt in its viewDidAppear method. Apparently, this messes with the dismissal of the presented view, and seemingly only in iOS 9 (well, maybe not only, but it seems to work fine in iOS 10).
Try with below code:
newsViewController.completionBlock = ^{
[self performSelector:#selector(Dismiss) withObject:nil afterDelay:0.3];
};
-(void)Dismiss
{
[self dismissViewControllerAnimated:YES completion:^{
}];
}

-(void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController not been called

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

Is it Possible to Dismiss MFMailComposeViewController Without User Interaction?

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

dismissViewControllerAnimated:completion: not executed

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!

Successive calls to UIViewController's presentViewController method

I recently encountered a hair-pulling situation in my iOS app, where I was trying to successively dismiss one presented UIViewController from my window's rootViewController, using:
[rootViewController dismissViewControllerAnimated:YES completion:NULL]
and present another one shortly thereafter (in another method, incidentally), with:
UIViewController *vc2 = [[[MyViewController2 alloc] initWithNibName:nil bundle:nil] autorelease];
[rootViewController presentViewController:vc2 animated:YES completion:NULL];
Problem was, I could never get the second view controller to show up. Turns out, as near as I can tell, dismissViewControllerAnimated:completion: needs that asynchronous block of "completion" time to pass, before presentViewController:animated:completion: will work properly again. This fact is not directly documented in Apple's docs, from what I can tell.
The solution I came up with was to wrap the dismissal with a method that specifies the selector you would want to call afterwards, like so:
- (void)dismissViewController:(UIViewController *)presentingController
postAction:(SEL)postDismissalAction
{
[presentingController dismissViewControllerAnimated:YES
completion:^{
[self performSelectorOnMainThread:postDismissalAction
withObject:nil
waitUntilDone:NO];
}];
}
And then I would call:
[self dismissViewController:self.window.rootViewController
postAction:#selector(methodForNextModalPresentation)];
Anyway, I wanted to post, as I looked around and hadn't seen anyone with this particular problem, so I thought it might be useful for people to understand. And also, I wanted to verify that I'm not hacking a solution that has a better design pattern for resolution.
Just for the sake of clarity. are you saying that this code doesn't work?
[myRootViewController dismissViewControllerAnimated:YES completion:^{
[myRootViewController pushViewController:newController animated:YES];
}];

Resources