I have a UINavigationController with a UITableView as my main menu. User clicks on a cell and a new view is pushed on the stack. In one case I push another UITableView that needs a toolbar. So on that 2nd tableView's init I setup the self.toolbarItems property with the correct items. But then I need to call [self.navigationController setToolbarHidden:NO animated:YES]; So it makes sense to call this in the viewDidAppear or viewWillAppear method. But I put it in those methods and find out (Also via NSLog) that they never get called. The same goes for hiding it in viewWillDisappear or viewDidDisappear. Why don't these methods get called? Where should I be doing this hiding/showing of the toolbar then?
I have noticed behavior where if a parent controller (like UINavigationController or UITabBarController) never get's viewWill/DidAppear called on it, it won't call it on the child controllers either. So make sure that in the code where you create the parent controller, you call viewWillAppear, show it, then call viewDidAppear. Then it should make those calls on it's child controllers as is appropriate.
Double check the parent controller is having those methods called, and call them yourself if they are not.
Yes Its true
you can do this by first write this code in
- (void)viewDidLoad {
self.navigationController.delegate = self;
}
And then write the code which you want to write in viewWillAppear
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if ([viewController isKindOfClass:[self class]]) {
//write your code here
}
}
Although you solved your problem, in case someone comes along in the future another problem could have been that you forgot the animated: argument to either method - that is to say, the format of the method needs to look like:
- (void) viewWillAppear:(BOOL)animated
i noticed the same issue in iOS7. When i'm using both tab bar (2 buttons A, B) and navigation controller.
A has two views. One with tableview and second displays data according to the selection from the table view.
B has is the only view.
Button which is refer to another separate view D, placed in both tab bar views (A & B) and in both views of A.
Problem arises when i click the button from tab item B, viewWillAppear and viewDidLoad not called.
So i solved this issue by presentModalViewController:animated: and to come back i used dismissModalViewControllerAnimated:, just when i go to view D from tab item B.
Related
I have a general question about viewWillAppear, I pushed newly a view consider view1, now viewWillAppear is called for that method, if view1 pushed another view (view2). Now if view2 is popped view1's viewWillAppear is called again, how can I come to know in which case its called i.e (is it because of newly pushing or popping of other view).
Hope my question is understandable :)
TIA
Frankly, I do not know how to determine that condition appropriately.
You could of course set some property of view1 in view2's viewWillDisappear method and re-set that in view1's viewWillAppear. Such like:
view1.m:
- (void) viewWillAppear ... {
...
if (self.wasPopped) {...}
self.wasPopped = NO;
...
}
view2.m:
- (void) viewWillDisappear {
view1.wasPopped = YES; //you would have to have a reference to view1 or fetch it from the navigation controller stack.
}
However, I strongly believe that this is not really the way you should go. Consider moving your code to the viewDidLoad method. viewDidLoad is called only once when the view(Controller) is created well before viewWillAppear is called for the first time.
Depending on your context, you want to use...
For viewWillAppear/viewDidAppear
isBeingPresented
isMovingToParentViewController
For viewWillDisappear/viewDidDisappear
isBeingDismissed
isMovingFromParentViewController
Just simply remember that whenever your view will appear on screen(it is going to be visible ) viewWillAppear method will be called.
In your case when view2 is popped again your view1 is going to appear on the screen that's why it's viewWillAppear is called again. Same way when you push new view controller, that new view controller will be displayed on the screen. Before displaying that view, that new view controller's viewWillAppear will be called.
I hope it makes sense.
You can use BOOL value and initialize it to FALSE.In viewWillAppear make that bool value TRUE and in viewWillAppear only check whether that bool value is TRUE or FALSE if it is true means the view is appearing for second time.
In a UIViewController subclass, I have the following methods:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// do something
myTextField.text = #"Default";
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// do something
[myTextField selectAll:self];
[myTextField becomeFirstResponder];
}
- (void)viewDidLoad {
[super viewDidLoad];
// do something
myTextField.delegate = self;
}
The NIB has been created using Interface Builder. The corresponding view controller object is pushed by the navigation controller through pushViewController.
The inteded behavior is to show a default text entry in a text field, to select the entire text and to set the text field as first responder. [Edit: I've noticed that selecting all and making first responder makes no sense as the selection would dissapear; still, I wonder why the methods behave as described next.]
However, while methods viewDidLoad and viewWillAppear are called, the method viewDidAppear is not called. Can anybody tell me why? Most questions I found on the web and here deal with both viewWillAppear/viewDidAppear are not working; I also understood that in subviews or programmatically created views these methods are not evoked; but this does not apply in case and also I wonder why one of these "lifecycle" methods is evoked and the other not.
Any idea? Thanks!
I had this issue happen to me: viewWillAppear was being called but viewDidAppear was not!
I finally figured out that this was because I had a tabBarController where I overloaded it's own viewDidAppear and forgot the [super viewDidAppear:animated];
It threw off every VC in every tab! adding that line back in fixed it for my other VC's.
Hope this helps someone!
There is one case when viewWillAppear will be called but viewDidAppear will not.
Suppose you have two viewControllers and you push from the first to the second controller. Then, using the swipe, you want to go back to the first one, both controllers are displayed at the same time and at that moment viewWillAppear will be called from the first controller.
This way, you do not finish the swipe and stay on the second controller and viewDidAppear will not be called from the first controller.
I had the same problem.
I had copy/pasted viewDidAppear to create viewWillAppear but had forgotten to change the super.viewDidAppear() call. This then seemed to stop viewDidAppear from being called.
It sounds like somewhere in your code you have missed or messed-up a call to the superclass.
The call to viewDidAppear: should always follow viewWillAppear: unless you are doing something custom, which you say you don't. I don't know why it doesn't work but here are a few ideas:
Could it be that you are doing something strange in one of the delegate methods for UITextFieldDelegate? It's unlikely that it would affect viewDidAppear: being called but it could be a culprit.
Have you loaded a lot of stuff into memory before pushing the view? I'm not sure what would happen if you got a memory warning between viewWillAppear: and viewDidAppear:.
Have you tried to do a Clean? Sometimes that can help.
In cases like these when it should work I usually create a new class and the introduce the functionality one at a the time to see if I can get it work that way. I tried your code in a new Navigation Based project where I added a new UIViewController with an outlet to the text field. Then I pasted the code from the question and it did work as expected.
This can be because you added a child view controller to your parent VC in viewDidLoad or viewWillAppear. The child's appearance prevents the call to viewDidAppear.
This is a crazy thing to do, and I only know because this was a bug in my code. I meant to add the child VC to this VC, not the parent VC.
It appears that ViewDidLoad() is sent to a ViewController only after its View is physically displayed (i.e. via NavigationController pushViewController), and not immediately after initWithNibName(). Is this a behavior I can rely on? I would like to get the chance to set the member variables of my view so that all the members are valid by the time ViewDidLoad() is invoked.
You can set up member variables and other such things in initWithNibName:bundle:.
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle {
if (self = [super initWithNibName:nibName bundle:nibBundle]) {
// set up ivars and other stuff here.
someIvar = someValue;
}
return self;
}
You are correct that viewDidLoad: is only sent when the view is physically displayed, i.e when it is added to some visible view (which may sometimes be never if the user does not reach that view). So it's useful to split the functionality and think about what you can do at init time and what happens at view load time.
As Marcelo Cantos notes in the comment, viewDidLoad: is generally a fine place to do all sorts of setup work, using the concept of "lazy loading," so that you defer the setup until as late time as possible.
viewDidLoad is called before a view controller is displayed for the first time, not immediately after initWithNibName. For example, if you have a tab bar controller, all of the child view controllers will be initd at launch, but viewDidLoad will only be called when you click on the appropriate tab the first time. It's generally a good idea to initialize memory-intensive items in viewDidLoad, so as to avoid using unnecessary memory.
I found that if I override initiWithNibName in the view controller, the viewDidLoad method is not called. I have to call it manually [self viewDidLoad]. But if I do not override initWithNibName: viewDidLoad is called. I am working with 4 view controllers in tab bar controller. the tab bar controller is loaded from another view.
Sorry to unearth an old thread, but this solved it for me...
-(void)viewDidLoad is only called after -(void)loadView has done its thing. In the docs for loadView:
The view controller calls this method when its view property is requested but is currently nil.
My view controller only has viewDidLoad called after its view is request by a UITabBarItem, meaning viewDidLoad is only called in the viewController once the tab bar button is pressed. I, like the OP, want viewDidLoad to be called directly after the nib is loaded, so it's contents (titles, etc) can be populated before the user clicks the tab button.
So, after calling "self = [super initWithNibName:#"nibName" bundle:nil];" in the view controller's custom initialiser, I immediately called '[self view]' afterwards. As the view is requested earlier than when it is requested by the UITabBarItem (which calls 'addSubview'), the view is initialised fully during initialisation, rather than when requested.
Hope this helps.
I need to update the parent view on an iPhone after popping a child view off the navigation stack. How can I setup the parent view to be notified or receive an automatic method call when the child is popped off the stack and the parent becomes visible again?
The user enters data on the child page that I want to display on the parent page after the user is done and pops the view.
Thanks for you help!
I just resolved this self same problem - and the answers above are almost correct, they just forgot about setting the delegate.
I have a root view controller that displays the size of a list, calls a child view controller that may alter the size of a list, and must update the size upon return.
When I create my parent view (SettingsView below), and add it as the root view of a UINavigationController, I make sure to set the UINavigationController's delegate before I display the view - that's the key part:
SettingsView *sv = [[SettingsView alloc] initWithNibName:#"SettingsView" bundle:nil];
UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:sv];
[nc setDelegate:sv];
In the parent view, implement the UINavigationControllerDelegate protocol:
#interface SettingsView : UIViewController <UINavigationControllerDelegate>
and provide the willShowViewController method:
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
// Your code to update the parent view
}
This is called after the child view is dismissed, and before the parent view is redisplayed.
I had the need to do something like this as well. In the ViewController that owned my UINavigationController, I had to implement willShowViewController, like this:
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
}
That method is called whenever the UINavigationController changes views. If I'm understanding your question correctly, I think this should do what you want.
I think there is some confusion here. UIViews are not pushed to and popped from the UINavigationController's stack. What is being pushed and popped is UIViewControllers, which in turn handle one or (more often) several views each.
Fortunately, the UIViewController has these methods:
-(void) viewWillAppear:(BOOL)animated;
-(void) viewDidAppear:(BOOL)animated;
-(void) viewWillDisappear:(BOOL)animated;
-(void) viewDidDisappear:(BOOL)animated;
These are called whenever the view is about to (dis)appear, or has just (dis)appeared. I works with tab bars, modal views and navigation controllers. (And it's a good idea to make use of these when you implement custom controllers.)
So in your case, if I understand correctly, you simply have to override the viewWillAppear: or viewDidAppear: method on what you call the "parent page" (which is presumably handled by a UIViewController) and put in code to update the appearance of the page to reflect the data just entered.
(If I remember correctly, you must make sure that the UINavigationController gets a viewWill/DidAppear: message when it is first displayed, in order for these messages to later be sent to its child controllers. If you set this up with a template or in IB you probably don't have to worry about it.)
Felixyz answer did the trick for me. Calling the view will appear method will run the code in it every time the view appears. Different from view did load, which runs its code only when the view is first loaded. So your parent view would not update itself if a child view altered the info displayed in the parent, and was then popped off the sack. But if the parents calls view will appear, the code gets ran every time the view shows back up.
Make sure to call the super method at the same time. Proper implementation would look like this:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(#"View Appearing");
}
If you need to notify one controller to another you may use delegation pattern as described here (see 2nd answer).
Unfortunately there is no automatic notification(AFAIK) for exact task as you described.
To meet your needs you may send message to delegate (i.e. to your parent controller) in viewWillDisappear function of your child controller.
I have a UIViewController that manages the display of some data. When the user wants to edit this data I push an edit UIViewController onto the stack. When the user is finished editing the top view controller is popped off the stack. What is the most elegant way to know that I need to update my display after the edit view is popped off?
I thought that I might be able to put the content update code into the viewDidLoad method of my data view, but this method isn't always called when my view displays, especially not when I'm navigating down the view stack.
I also considered setting up my data view controller as a delegate for the UINavigationController and wait for – navigationController:didShowViewController:animated: to be called, my concern is that there may be other view controllers that need to be notified when they're displayed and it will turn into a minor headache managing which controller should be receiving the didShowViewController message.
I think viewWillAppear will do the trick.
Otherwise your edit view controller could call a new method of delegate pointing to your parent controller. In there, you can update the data model and display. For instance:
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
if ([delegate respondsToSelector:#selector(editEntryByTitle:)])
[delegate performSelector:#selector(editEntryByTitle:) withObject: textField.text];
[textField resignFirstResponder];
[self dismissModalViewControllerAnimated:YES];
return YES;
}