I've got a view controller called CommentEditorViewController presented modally and here is the code to dismiss it:
- (IBAction)doneButtonTouched:(id)sender
{
NSLog(#"doneButtonTouched");
[self dismissViewControllerAnimated:YES completion:^{
[[ImageStore sharedStore] setComment: self.textView.text forImageWithIndex:self.imageIndex];
}];
}
It works completely fine, but the problem is that the parent view controller's (which is called PhotosViewController) viewWillAppear and viewDidAppear methods are being executed before this block. And I can't figure out, how to execute code right after this block completion. Should I do all this stuff in the block? If it is so, how should I do this?
You have to move that code before the dismissViewControllerAnimated:completion: line , because the code written in completion block will execute when the viewcontroller is really eliminated , that just might be after viewDidAppear of parent viewcontroller.
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 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...
I'm implementing my own 'back' button. Where onClick, the following code is executed in the ViewController (VC) being dismissed:
Dismiss current VC (VC#1)
Pop current VC (VC#1) off my custom navigationStack
Get the last VC (VC#2) from the navigationStack, and present it using
presentViewController
What happens is the back works visually works - i.e. current VC disappears, previous VC appears. However, the viewDidLoad method is not called. So the screen isn't updated with data updates from viewDidLoad.
[self dismissCurrentViewController:self completion:^{
[TWStatus dismiss];
FHBaseViewController *vcToDisplay = [[FHDataManager sharedInstance] popNavigationStack];
[vcToDisplay.homeVC presentViewController:vcToDisplay animated:NO completion: ^{ }];
}];
Questions:
I was under the impression that viewDidLoad always gets called when presentViuewController is used??
I 'build' the screen using a method called ONLY from viewDidLoad in VC#2. How is iOS displaying the screen without coming into viewDidLoad?
btw, I'm not using storyboards. Any help is appreciated!
My guess is that viewWillAppear is being called but viewDidLoad is not, at least not when you expect it is. viewDidLoad should be called once, but depending on how you're managing the view controllers, viewDidLoad may not be triggered every time your view appears (which happens after loading).
The completion handler is called after the viewDidAppear: method is called on the presented view controller. from presentViewController doc
so put this in your code with a breakpoint on the call to super and verify it is getting called when this transition occurs.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
edit: since you verified that viewWillAppear is getting called, then I would say that it's coming down to how you are managing the view controller life cycle. Even with a standard UINavigationController, viewDidLoad is not called when a view is shown as a result of popping items on the navigation stack. I would move your logic to viewWillAppear if you are dead set on not using UINavigationController
When I make a back button pragmatically I use:
[self.navigationController popViewControllerAnimated:YES];
This will invoke the viewDidLoad method. Use that instead of your current code.
I have a modal view controller presented and just before I dismiss it, I need to call a delegate method which tells the parent view controller to update. (As methods like viewWillAppear are not called when dismissing a modal view controller).
So my code looks like this:
[delegate addEquipmentDidSave:YES];
[self dismissViewControllerAnimated:YES completion:nil];
Very simple. Send a message back, saying, update now! And then just dismiss the view. However, while both of these lines are called, the delegate method never runs. So I check that the delegate it set correctly. When I present the modal view I set the delegate, so its all connected.
Its as if the delegate method isn't getting a chance to run before the view is dismissed. Is this possible? What do you think might be the issue?
Thanks.
Before calling your delegate method first check whether it's available or not
if ([self.delegate respondsToSelector:#selector(addEquipmentDidSave:)] )
{
NSLog("Yes it's available");
[self.delegate addEquipmentDidSave:YES];
}
[self dismissViewControllerAnimated:YES completion:nil];
Do you see that last parameter, the one called completion? That block is called after the view controller is dismissed. Do what you want to do in there.
In my ios app, I begin by loading the SecondViewController using the code shown below. When that view controller appears, I also run viewDidAppear as shown in the code snippet below.
That works ok. From there, I load my FifthViewController and do some calculating which needs to be returned to the SecondViewController.
Unfortunately, when I return to SecondViewController via the same exact method, the viewDidAppear method does not run.
Any suggestions on how to fix that would be appreciated.
-(IBAction) loadSecondView:(id)sender
{
NSLog(#"In loadSecondView method \n");
[secondViewController viewDidAppear:YES];
[self clearView];
[self.view insertSubview:secondViewController.view atIndex:1];
}
If you want something to be performed every time after dismissing a viewcontroller, you should use delegate method. It is much more specific than viewDidAppear or viewWillAppear.
Here's a website where you can get started.