I am modally presenting a ViewController from another one. The completion block is being called immediately, while the presented VC is still presented. Why would that be? Code follows.
UIStoryboard* sb = [UIStoryboard storyboardWithName:...];
UINavigationController* nc = [sb instantiateViewController...];
nc.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentViewController:nc
animated:YES
completion:^{ /* Called immediately! */ }];
There is stuff I want to do only when the presented VC is finished. I have a workaround but my understanding is I should be able to do it in the completion block.
The completion block is called once the viewController has been presented, as per Apple's documentation. This is the intended behavior: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/index.html#//apple_ref/occ/instm/UIViewController/presentViewController:animated:completion:
I'd suggest wiring up some sort of callback in your viewController's viewWillDissappear to perform an action if necessary.
OK I had RTFM and saw this: "completion: The block to execute after the presentation finishes." Which really does imply what I thought. Ambiguous at best.
HOWEVER... elsewhere we read "The completion handler is called after the viewDidAppear: method is called on the presented view controller." Which is quite another thing, and confirms Ian.
So my workaround is actually the right way to do it...
Related
I have a view controller(VC1) embedded in a navigation controller(NAV1). In its viewWillAppear method, I make a call to modally present another view controller. In one case I need the new view controller(VC2) to be presented with animation, and in another case it should be presented without animation. VC2 is also embedded in its own navigation controller(NAV2).
All is fine when the animation flag is set to TRUE. When I set the flag to FALSE, couple of things go wrong:
1. I get the following warning in the console: Presenting view controllers on detached view controllers is discouraged
2. When I move back from VC2 after calling dismissViewControllerAnimated:FALSE completion:nil the viewWillAppear method of VC1 does not get called. It gets called if the animation flag is set to TRUE.
In VC1:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self performSelector:#selector(importStuff:)
withObject:nil];
}
-(void)importStuff
{
//instatntiate VC2
//instantiate NAV2
[Utility presentViewController:pNavController
fromViewController:self
animated:FALSE
completion:nil];
// the above method calls the presentViewController:animated:completion: method
}
IN VC2:
[Utility dismissViewController:self
animated:FALSE
completion:nil];
The above method calls the dismissViewControllerAnimated: method.
Not a solution but a workaround:
You can easily postpone any UI operation to the very next event loop by using afterDelay:0:
[self performSelector:#selector(importStuff:)
withObject:nil
afterDelay:0];
This will give a chance to the current operation to complete.
Furthermore, a delay of 0.4 will match the OS. However, whatever delay you use (other than 0) is a kludge and not guaranteed to work under every situation, device and memory load, etc.
Instead, you should revisit your approach.
Change your design:
Do not run the risk of encountering this animation race in the first place.
You have a couple of options, including:
Controlling the transition animation yourself and waiting for its completion prior pushing another view controller (using a completion signal or completion block)
Changing your methodology entirely to avoid this conundrum altogether
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self performSelector:#selector(importStuff) withObject:nil afterDelay:0.1];
}
-(void)importStuff
{
//instatntiate VC2
//instantiate NAV2
SecondViewController *viewMe=[self.storyboard instantiateViewControllerWithIdentifier:#"second"];
[self.view.window.rootViewController presentViewController:viewMe animated:NO completion:nil];
// the above method calls the presentViewController:animated:completion: method
}
i have a view controller and i need to dismiss it and present it back in same time.
i had tried dismiss it and call back the view controller but not working.
[self dismissViewControllerAnimated:YES completion:nil];
UIStoryboard *storyboard=[UIStoryboard storyboardWithName:#"Main" bundle:nil];
ExpandViewController *expandView=
[storyboard instantiateViewControllerWithIdentifier:#"ExpandViewController"];
expandView.delegate=self;
[expandView setEventDict:dict];
[self presentViewController:expandView animated:YES completion:NULL];
I am not exactly sure what outcome/functionality you are looking for in your question, but #matt is correct. However, you may be looking to have this happen seamlessly. Therefore you could use child view controllers instead of presenting the view controller using the [self presentViewController:VC animated:animate completion:nil] method.
Adding child vc:
[self addChildViewController:myVC];
[self.view addSubview:myVC.view];
[myVC didMoveToParentViewController:self];
Removing child vc:
[myVC willMoveToParentViewController:nil];
... remove subview.
You can set up a delegate between the two controllers to tell the parent when to dismiss the view to make things easy. You can also add the subviews at different indexes using [self.view insertSubview:myVC atIndex:index] or the other possible functions such as the insert above subview etc, to have one subview be added before dismissing the other to give a more seamless transition.
Hope this helps!
You can't present a view controller until the currently presented view controller has finished being dismissed. You won't know this has happened until the completion handler from your dismissal is called. Your mistake is that the completion handler is nil. Instead, provide a completion handler (in your first line), consisting of the remaining lines of your code. Thus, they will execute after the dismissal finishes.
[self dismissViewControllerAnimated:YES completion:^{
// ... the rest of your code goes in here ...
}];
I am loading LaunchViewController as the root view controller in my AppDelegate's application:didFinishLaunching method:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"LaunchViewController" bundle:nil];
LaunchViewController *launchViewController = [storyboard instantiateInitialViewController];
launchViewController.managedObjectContext = [CurrentSession mainQueueContext];
self.window.rootViewController = launchViewController;
[self.window makeKeyAndVisible];
LaunchViewController communicates with a web server to fetch some data. I am using AFNetworking library for asynchronous communication with the web server. In the success callback after fetching the data, I am presenting the LoginViewController. My understanding is that the callbacks in case of AFNetworking are performed on the main thread. Nevertheless I used performSelectionOnMainThread just to see if that would resolve the issue:
Inside callback:
[self performSelectorOnMainThread:#selector(presentLoginView) withObject:nil waitUntilDone:NO];
presentLoginViewmethod:
- (void)presentLoginView {
LoginViewController *loginViewController = [[LoginViewController alloc] initWithNibName:#"LoginViewController" bundle:nil];
loginViewController.managedObjectContext = self.managedObjectContext;
[self presentViewController:loginViewController animated:YES completion:nil];
}
The login view is not presented and in the console I see following message:
Warning: Attempt to present <LoginViewController: 0x78e47c30> on <LaunchViewController: 0x78eb4a60> whose view is not in the window hierarchy!
:-(
UPDATE
I put a breakpoint in viewDidLoad, viewDidUnload, viewWillAppear and viewDidAppear methods of the LoginViewController. viewDidLoad was hit. Others were not.
Why would the view get loaded but not appear?
That's probably happening because the AFNetworking execute the callback very fast, for instance before the view of the view controller is loaded, maybe because you are using a local server or another configuration in viewDidLoad that is slow. In this case I would recommend to move the code to viewWillAppear or viewDidAppear since in that moment the view would be in the hierarchy and therefore you would be able to present another ViewController from there.
In my AFNetworking success callback I was setting some of the information obtained from the web server into NSUserDefaults. And then I was presenting the LoginViewController.
My AppDelegate was listening to NSUserDefaultsDidChangeNotification notification. The handler for this notification would set the launchViewController as the root view controller.
So, in the AFNetworking callback while I was asking LaunchViewController to present LoginViewController I was also sending off NSUserDefaultsDidChangeNotification which would cause the AppDelegate to load the LaunchViewController.
I never saw even a flicker of LoginViewController ever. So, I think the AppDelegate handler was getting fired first and then the presentLoginViewController was getting called. And this somehow lead to the views not being added to window hierarchy message.
I work on a legacy application, and have found out, that my view[Will/Did]Disappear methods are not always fired properly.
The case is, I have a (custom) UIViewController set as rootViewController in AppDelegate. This rootViewController has a UINavigationController, which has two view controllers pushed on it. When the user presses the home button, the user is logged out. When he later returns to the app, the application calls [UINavigationController popToRootViewControllerAnimated:YES] and then displays a modal UIViewController for logging in.
The problem is: When I push/pop on the UINavigationController normally, my viewWillDisappear method is called properly. But when I use the popToRootViewControllerAnimated: method, viewWillDisappear is not called on any of the viewControllers that are popped off.
Searching on the internet has only given two possible reasons:
If using a UINavigationController as a subview, you must call view[Will/Did]Disappear yourself
Not calling the proper super methods
None of these suggestions are the case in my app. And I have no idea where to look. Anybody has a suggestion to what has been done wrong in the app?
The view probably wasn't onscreen. It has to be onscreen (visible) for the viewWillDisappear: method to be called. If it's coming back from the background, it wasn't visible.
You could try using willMoveToParentViewController: which is called when the view controller is removed from its parent.
such useful to me
[nav performSelector:#selector(popToRootViewControllerAnimated:) withObject:nil afterDelay:0.0];
I rewrote UITabBarController
- (void)setSelectedIndex:(NSUInteger)selectedIndex {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
UINavigationController *navigationController = [originalViewController as:[UINavigationController class]];
if (navigationController.presentedViewController) {
[navigationController dismissViewControllerAnimated:NO completion:^{
[navigationController popToRootViewControllerAnimated:NO];
}];
}else if (navigationController.topViewController){
[navigationController popToRootViewControllerAnimated:NO];
}
});
}
I'm trying to dismiss a view controller like this:
[composeViewController dismissViewControllerAnimated:YES completion:^{
NSLog(#"Hello"); // Never outputted
}];
The view controller is dismissed, but for some reason the completion block is never called.
I have never had any issues with completion block not being called with other view controllers.
This view controller is "special" though, because it's added as a child view controller (which I have not worked with previously in my app). Does this impose any side effects why the completion block is not called?
It's added like this:
UIViewController *rootVC = [UIApplication sharedApplication].delegate.window.rootViewController;
[rootVC addChildViewController:self];
[rootVC.view addSubview:self.view];
[self didMoveToParentViewController:rootVC];
Found out what the issue was: the 3rd party view controller I was using had overridden - (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion without actually calling completion()
If you present a modal, is the view controller that receive the message (or the top in hierarchy , I still didn't get that) that handles all the process of adding the child v.c. swapping view etc. It seems that you are doing a mix of the two techniques. Just use one. So present it using - (void)presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion ad dismiss it using dismissViewController let the view controller manages everything.