When I program without a nib, I am under the impression that I need to call loadView to initialize my view, like this:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nil bundle:nil];
if (self) {
// Custom initialization
[self loadView];
}
return self;
}
(I have set nibNameOrNil = nil, since there is not nib.)
Then, I set up the view, like this:
- (void) loadView {
self.view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 367)];
[self viewDidLoad];
}
This is do everything else in viewDidLoad.
I'm still unsure if I am supposed to make the calls to loadView and viewDidLoad in this way. They are not getting called automatically.
What is confusing is in the documentation for the UIViewController class reference:
loadView Discussion
You should never call this method directly. The view controller calls
this method when the view property is requested but is currently nil.
If you create your views manually, you must override this method and
use it to create your views. If you use Interface Builder to create
your views and initialize the view controller—that is, you initialize
the view using the initWithNibName:bundle: method, set the nibName and
nibBundle properties directly, or create both your views and view
controller in Interface Builder—then you must not override this
method.
So, I don't understand how loadView gets called if I should never call it directly.
The default implementation of this method looks for valid nib
information and uses that information to load the associated nib file.
If no nib information is specified, the default implementation creates
a plain UIView object and makes it the main view.
I don't understand how that works -- the creation of a pain UIView.
If you override this method in order to create your views manually,
you should do so and assign the root view of your hierarchy to the
view property. (The views you create should be unique instances and
should not be shared with any other view controller object.) Your
custom implementation of this method should not call super.
If you want to perform any additional initialization of your views, do
so in the viewDidLoad method. In iOS 3.0 and later, you should also
override the viewDidUnload method to release any references to the
view or its contents.
Okay, so far it doesn't say how viewDidLoad is called. So for viewDidLoad:
viewDidLoad Discussion
This method is called after the view controller has loaded its
associated views into memory. This method is called regardless of
whether the views were stored in a nib file or created
programmatically in the loadView method. This method is most commonly
used to perform additional initialization steps on views that are
loaded from nib files.
Called by what?
Since These methods are not getting called automatically in my code, I am left to think that I have to call them myself. But I still don't get a clear understanding form the documentation that this is the right thing to do.
As the documentation says, you should not call these methods yourself.
How this is meant to work is that UIKit's code will call your loadView method if and when it actually needs to present that controller's view hierarchy on screen.
Specifically, if any code attempts to read your view property on your view controller, and that property is nil, then UIViewController's code will call the loadView method. If you don't implement loadView yourself, then the default implementation will fall back to attempting to load the view hierarchy from the nib.
This all happens automatically when you attempt to present your view controller and the view is nil. (This can happen multiple times while your app is running if your view controller has to unload its view in response to memory pressure, another behavior you get for 'free' and that you don't want to call yourself)
If these methods are not being called in your view controller, then there must be something wrong with how you are presenting this view controller, which is preventing the framework code in UIViewController from calling these methods. Post that code and someone can help you figure out that bug.
Before you attempt to fix that bug, though, you should remove these from your code:
[self loadView]
[self viewDidLoad]
And then in your own implementation of viewDidLoad, you will want to call the superclass' implementation with [super viewDidLoad]
Remember that in loadView your only responsibility to set self.view to some valid view. That's it. UIKit code will then see that and call viewDidLoad for you.
Hope that helps.
The answer by Firoze Lafeer is correct, I just want to show how to force load the view property the correct way:
(void)self.view;
You should not do this! If you must, you are doing something wrong, but do not call -loadView and -viewDidLoad yourself at any circumstances.
Related
I've got a question regarding the two mentioned methods, since in my tests I don´t make clear the order they are called. I thought that, firstly, viewDidLoad is called when the viewController is loaded for first time (as the name indicates), and inmediately after the init method. Then, I thought that once viewDidLoad returns, viewWillAppear is called. If you display another viewController, and then you return to this one, then it should be already loaded and only viewWillAppear will be called.
However, while developing I make the impression that there is no order when calling viewDidLoad and viewWillAppear... I couldn´t find a clear description of this lifecycle in Apple's documentation, how does this actually work?
Thanks!
I would like to add to Caleb's answer: Don't confuse the view controller and the view! The name viewDidLoad clearly indicates that the method is invoked after the view has been loaded. It is the view controller that does the loading.
Some pointers regarding the lifecycle of views and the order in which messages are sent:
Not an official Apple document, but I find this diagram really useful because it includes pretty much all of UIViewController's lifecycle overrides.
In the section Resource Management in View Controllers from Apple's "View Controller Programming Guide" there is a flowchart that depicts how views are initially loaded. It explains loadView and viewDidLoad, also in conjunction with storyboards.
The section Responding to Display-Related Notifications from Apple's "View Controller Programming Guide" explains how to respond to views appearing and disappearing (viewWillAppear: et al)
If you are planning on implementing a container view controller: The UIViewController class reference has a good overview of how messages need to be sent by your subclass.
I'm stopping here. You can find more stuff yourself by googling for "uiviewcontroller life cycle".
-viewDidLoad is called when the controller loads its view, which is not necessarily right after initialization. View controllers don't load their views until they need them, either to display or for any other reason.
-viewWillAppear is called just before the view is displayed. This will be after -viewDidLoad, but you don't know exactly how long after. -viewWillAppear is called every time the view is displayed; -viewDidLoad will only be called a second time if the view is unloaded at some point (such as didReceiveMemoryWarning). These days that's unusual, but it can happen.
Or if the viewController is set to nil, which can usually happen if a view controller is kicked off the navigation stack, and therefore next time it is brought to the navigation stack it needs to call -viewDidLoad again.
I thought that, firstly, viewDidLoad is called when the viewController
is loaded for first time (as the name indicates), and inmediately after the init method
No. The name indicates that the controller's view has been loaded (not the controller itself). Actually the docs state that this method will get called after the view hierarchy has been loaded into memory (either via loadView or through a nib for example).
Then, I thought that once viewDidLoad returns, viewWillAppear is
called
Again, no. loadView (and as a consequence viewDidLoad) method will get called the first time that view property is to be accessed and is nil (which is the case when you're initializing a controller). Think of this simple scenario:
MyViewController *vc = [[MyViewController alloc] init];
UIView *view = vc.view; // <= loadView & viewDidLoad will fire but it certainly didn't appear...
However, while developing I make the impression that there is no order
when calling viewDidLoad and viewWillAppear...
Well there is an order. We know for sure that viewWillAppear will always be called after viewDidLoad (if both of them are to be called of course).
As you said, ViewDidLoad is only calling once after loading the view. So we can initialize the instances in the viewDidLoad. It is mainly meant for the initialization.
viewWillAppear will invoke whenever we reach to this view. So if there is any changes in UI, we can done it in viewWillAppear.
I ran a trace on when all these calls are made: http://thecodist.com/article/ios_arc_storyboards_and_uiviewcontroller_trace
The methods applicationDidBecomeActive, loadView, and viewDidLoad will get called at appropriate times in an iOS app. For loadView and viewDidLoad, it looks like it is:
-(void) someMethod {
//...
[viewController loadView];
[viewController viewDidLoad];
}
Is that how they get called and what is the class that call them? (Is there source code that can show the flow of the starting of an app? A lot of times, we can only see the header files but not the source code).
If I understood well your question, you would like to know about the application lifecycle, is it true?
Well, I guess there is no source code provided by apple that can display you how it looks like.
If you want to know how happens when an application starts, I suggest to read about app-launch-sequence-ios-revisited by Oleb. It's a very good post.
About the methods you wrote, these methods shouldn't not called manually. It's the framework (through the iOS) that calls them for you.
The methods loadView and viewDidLoad are methods that are called during the UIViewController lifecycle.
You use (override) loadView when you cannot create a storyboard or a nib file. In this manner you can provide to your UIViewController a fresh view. From Apple doc:
If you cannot define your views in a storyboard or a nib file,
override the loadView method to manually instantiate a view hierarchy
and assign it to the view property.
In other words:
- (void)loadView
{
UIView* myCustomView = ... // create the view here
self.view = myCustomView;
}
About the viewDidLoad method, this is called when a view has been set up in memory. Once done you are sure that outlets, for example, are set up and you can perform additional initializations.
From Apple doc:
This method is called after the view controller has loaded its view
hierarchy into memory. This method is called regardless of whether the
view hierarchy was loaded from a nib file or created programmatically
in the loadView method. You usually override this method to perform
additional initialization on views that were loaded from nib files.
In other words:
- (void)viewDidLoad
{
[super viewDidLoad];
// additional initializations here
}
Finally, about applicationDidBecomeActive method (or delegate if you want), this is called to let your application know that it moved from the inactive to active state.
I suggest you to read UIApplicationDelegate and UIViewController class references.
If you want to simply verify the sequence call, override the methods and put a NSLog there.
Hope it helps.
Apple's docs do not say what the correct implementation is for loadView.
I've found that if you implement loadView like this:
- (void)loadView
{
self.view = [[UIView alloc] init];
}
...then you get different behaviour than if you don't implement it at all. In particular, in one 20-line project, I've found that viewWillAppear is called with a zero-size frame for self.view - unless you use Apple's default version of loadView.
Looking on Google, there are lots of "tutorials" that provide obviously-wrong loadView implementations - e.g. force-setting the size to (320,480) because the tutorial author "found that it works if I do this".
I'd like to know what the correct implementation should be.
NB: in my example above, I'm adding it to the view hierarchy inside AppDelegate like this:
[self.window addSubview:(UIViewController*).view];
I believe that in the presence of a UINavigationController or UITabBarController, Apple does some extra magic that - as a side-effect - causes a one-line loadView implementation to work OK. But I want to write it correctly, so that it always works!
NB: I've tried setting the autoresizing mask on the root view, but it doesn't change what happens:
- (void)loadView
{
self.view = [[UIView alloc] init];
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}
Default implementation of -loadView creates the view or loads NIB. As far as I know, there is no way to know the final size of the view at time of creation in -loadView. So the default view size is set to UIScreen.mainScreen.bounds. This is because it may be difficult to work with zero frame view in -viewDidLoad and other methods.
Your one-line implementation may look like this:
- (void)loadView {
self.view = [[UIView alloc] initWithFrame:UIScreen.mainScreen.bounds];
}
You don't need to set the autoresizing mask, because you don't know in what context the view will be displayed. The caller is responsible to set you correct frame, autoresizing mask and similar properties.
Imagine this in a UINavigationController method:
// we are pushing new VC, view is accessed for the first time
pushedVC.view.frame = CGRectMake(...);
It is setting the correct frame, but your -loadView is called just before that -setFrame:. So during -viewDidLoad you have temporary non-zero frame, just to be able to setup subviews and internal autoresizing. After this, the correct frame is set to you and in -viewWillAppear: you have final frame.
First, there is no 'default' implementation of loadView...that method is specifically there for you to override. I do agree that Apple's docs can be a little unclear though. But loadView is called by default whenever the view of the navigation controller is accessed and no view exists (for example: UIView *view = viewController.view). It can also be called manually. But in no situation will loadView have the correct dimensions...that is, in fact, impossible. loadView is called in order for the parent view controller to get the view in the first place so it can size it appropriately. Then once it gets the view it calls viewDidLoad. This is the only code path they can use because views can load from the loadView method or the nib and they must provide a place for additional setup when views are loaded from a nib. Finally, the parent controller will resize the view and call viewWillAppear only when the view will actually appear. For example, if you push a controller on a navController that's off screen, it won't call viewWillAppear until the navController itself is placed on screen. This is done because there's no point in running that code until the controller is actually visible. This is also why you can only ever get the correct dimension in the viewWillAppear method.
Now, you noticed that if you add a controller to a standard controller none of this stuff happens. This is because view controllers aren't really intended to contain other view controllers per say. Now in iOS 5, they explicitly support the use of Container View Controllers...which is essentially a view controller that IS designed to contain other view controllers. They added a few 'convenience' methods in iOS 5 to help with this but it's not strictly necessary. The jist of all this is: if you want to add one view controller to another, you will have to manually setup all the appropriate calls to the child view controller (all loading methods, rotation events, memory warning etc). In other words, you have to make your own container view controller. When you do, though, keep in mind what I said before about the code path. It's important that you call child controller methods in the same order Apple does or stuff won't work right.
Here's some links to info:
http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/Reference/Reference.html -Scroll down to: Implementing a Container View Controller
Also here for the view controller life cycle, which will help you figure out which calls need to be made in which order: http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/ViewLoadingandUnloading/ViewLoadingandUnloading.html#//apple_ref/doc/uid/TP40007457-CH10-SW1
I do recommend reading the entire View Controller Programming Guide....you can gleam a lot of information from there: http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/Introduction/Introduction.html#//apple_ref/doc/uid/TP40007457-CH1-SW1
When my view controller is first presented, I want it to potentially update a cache that provides the data for that view. However, when the user taps the back button from a deeper view controller to return to this view controller, I don't want to update the cache again.
Which event should I be using?
in init, I don't have all the parameters I need yet.
viewWillAppear will be fired every time the view will appear.
viewDidLoad will be fired every time the view has been loaded from the nib, which I believe could happen a second time if there's a memory warning. (Or is this wrong?) Since this is not a memory resident cache, it seems the wrong place to handle this.
having the caller call something extra is inelegant, if there's a built-in way to handle this.
To clarify, this is not a memory resident cache. This is parsing an XML file to binary. The binary is loaded and unloaded in viewDidLoad and viewDidUnload. This is a prerequisite for that step, making sure the binary is up-to-date prior to it being loaded.
Using init may work, but I would recommend a simple subclass of UINavigationController. Create a new method called setRootTableViewController:(UITableViewController *)controller, or something like it. In the method implementation call this:
[controller.tableView reloadData];
[self pushViewController:controller animated:NO];
reloadData will call all of your delegate and data source methods, and use them to update the table. If you want a special method call on your table view controller instead, you could change the method declaration to setRootTableViewController:(CustomTableViewController *)controller (or whatever your custom table controller is called), and replace the reloadData line with one that calls that method.
Then, in your app delegate, instead of creating a UINavigationController and adding your custom view controller, create one of these, and call this method to add the first view.
However, if you are using a nib to set the rootViewController, you can just override initWithRootViewController:(UIViewController *)controller, as I imagine that is what the nib will call to set the first view in the stack:
- (id)initWithRootViewController:(UIViewController *)rootViewController {
if ((self = [super init])) {
[(CustomController *)rootViewController doSomethingSpecial];
[self pushViewController:rootViewController];
}
}
Hope this helps!
I face a strange situation. In my controller, viewDidLoad is calling before init. Is there any technical reason behind that?
The viewDidLoad method is being called when accessing self.view inside the init method (since self.view should not yet be loaded from the nib the process seems to be fasten so it doesn't return nil).
I know this is a bit old post, but I'll post my point of view anywhere because I think it could help somebody.
Well, I've been in this same situation. I thought that viewDidLoad was being called before init method in my view controller class. But what was really happening was not that: the flow starts on init method, but jumps to viewDidLoad when calling [super init*], so my log messages in viewDidLoad method were being displayed first that those in my custom initialization.
I think that's it. I hope this to save some time to someone.
[Sorry for my English]
NOTE for UITabBarController:
I don't know what kind of UIViewController caused this for you but I faced a similar case with UITabBarController.
I thought it might help another one facing it with UITabBarController.
As far as I know all viewControllers call init before viewDidLoad, except for the UITabBarController and its subclasses.
As Andrew claims here, UITabBarControllers call loadView inside [super init] method, which causes the call to viewDidLoad. So the viewDidLoad method will be called before init has finished its job.
If you have some thing to setup in viewDidLoad you should perhaps do it inside init method after the call to [super init].
When you initialise a UIViewController from code, you use -initWithNibName:bundle:, whereas when it is initialised from a XIB, the XIB loading code will call -initWithCoder:. One, and only one of these two methods will be called, and they will definitely be called before -viewDidLoad.
There's no conceivable way that -viewDidLoad could be called first, unless you are calling it yourself (which you should never really do).
No, the viewDidLoad message is always called after init.
Are you sure init is called at all? There are several init methods especially for UIViewController, maybe another one is called instead making you think differently.
If you need more information, please paste the code of viewDidLoad and all of your init methods, and tell us how it is loaded (i.e. with code) or from a nib.
If your ViewController is being loaded from your main nib file, then most likely it is initWithCoder that is being called to initialize the controller.