How often is viewDidLoad called? - iphone

When the user switches to another program and then back again., the original program's view will be replaced by a new view from another program. So when the user switches back to the original program, would viewDidLoad be called the second time ?
Am asking this because if this is the case, then the initialization code placed inside viewDidLoad would be executed every time the user switches the screen back and forth. And this could result in reseting views and loosing unfinished works of the user ...

Don't do view controller initialisation in viewDidLoad. This is a common mistake.
For stuff that should only happen once when the view controller is loaded, do it in the controller's init method, like this:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)bundleOrNil
{
if ((self = [super initWithNibName:nibNameOrNil bundle:bundleOrNil]))
{
//do your initialisation here
}
return self;
}
The initWithNibName:bundle: method is called before the view is loaded from the nib, and is only called once in the lifespan of the view controller.
The controller's view can be loaded and unloaded multiple times during the lifespan of the controller and viewDidLoad will be called every time. It may be unloaded whenever it's not on screen, usually if memory is low.
If you do set stuff up in viewDidLoad (e.g. adding subviews programmatically) you should always unset them again in viewDidUnload.
Think of viewDidLoad and viewDidUnload as being like the init/dealloc for the view property of the view controller. For stuff that relates to the views, create and release it in those methods. For stuff that relates to the controller itself, create and release it in initWithNibName and dealloc.
UPDATE: On iOS 6 and later, viewDidUnload is never called any more (unless the view is explicitly set to nil in the code), and so viewDidLoad will typically only be called once in the life of a view controller. This makes the advice above less critical, but it's still best practice, and still necessary if you need to support iOS 5 and earlier.
UPDATE 2: If you are loading your view controller from a Storyboard (which is now the recommended practice) instead of creating it programmatically then initWithNibName:bundle: will not be called. Use initWithCoder: or awakeFromNib to initialize your controller instead.

#Nick Lockwood provides excellent information, but there are a few more things to remember.
First, initWithNibName:bundle: is not called if the view controller is instantiated from a nib file or storyboard. In that case, initWithCoder: and awakeFromNib are called instead. This situation used to be somewhat uncommon on iOS, but with the addition of storyboards it is now much more common for view controllers to bypass initWithNibName:bundle:.
I recommend putting non-UI initialization code in a separate method (I call mine setup) and call it from both initWithNibName:bundle: and awakeFromNib. But I only do this if it's important that that initialization only run once. Otherwise I put it in viewWillAppear: to be as lazy-load as possible.
Second, you should not do anything that references self.view in init... or awakeFromNib. You should never reference self.view until viewDidLoad is called (otherwise you will force the nib file to be loaded sooner than it is needed). UI-related things should go in viewDidLoad if they're related to setting up the views, or viewWillAppear: if they're related to configuring the views (i.e. loading them with data).
So the way I usually set these things up:
#implementation
- (void)setup {
// Non-UI initialization goes here. It will only ever be called once.
}
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle {
if ((self = [super initWithNibName:nibName bundle:bundle])) {
[self setup];
}
return self;
}
- (void)awakeFromNib {
[self setup];
}
- (void)viewDidLoad {
// Any UI-related configuration goes here. It may be called multiple times,
// but each time it is called, `self.view` will be freshly loaded from the nib
// file.
}
- (void)viewDidUnload {
[super viewDidUnload];
// Set all IBOutlets to `nil` here.
// Drop any lazy-load data that you didn't drop in viewWillDisappear:
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Most data loading should go here to make sure the view matches the model
// every time it's put on the screen. This is also a good place to observe
// notifications and KVO, and to setup timers.
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// Unregister from notifications and KVO here (balancing viewWillAppear:).
// Stop timers.
// This is a good place to tidy things up, free memory, save things to
// the model, etc.
}
- (void)dealloc {
// standard release stuff if non-ARC
[[NSNotificationCenter defaultCenter] removeObvserver:self]; // If you observed anything
// Stop timers.
// Don't unregister KVO here. Observe and remove KVO in viewWill(Dis)appear.
}
#end

-viewDidLoad will be called once whenever the view controller needs to load its view hierarchy. Obviously, that'll happen the first time that the controller accesses its view. If the view controller later unloads its view, then -viewDidLoad will be called again the next time the view is loaded. A view controller won't unload its view just because the view is hidden, but it might do so if memory starts to run low.
A view controller should know the state of its views and be able to set them up as necessary in its -viewDidLoad method. Views shouldn't be used to store state -- nothing should be irrevocably lost just because the view is unloaded.

So when the user switches back to the original program, would
viewDidLoad be called the second time ?
(Above is from the op)
In those cases there are two methods to be called:
- (void)applicationWillEnterForeground:(UIApplication *)application;
reopening a backgrounded app (from task manager or from springboard again)
unlocking device which is locked when the app is active.
- (void)applicationDidBecomeActive:(UIApplication *)application
after phone calls
notification center dismissal
task manager dismissal (double tap home button & double tap again)

From the docs:
This method is called after the view controller has loaded its associated views into memory.
So, it is called whenever the view controller has its views loaded into memory. This could be the first time the view is loaded and never again, or every time the view is made visible if your view unloads constantly (viewDidUnload due to memory constraints, etc.)

Related

Start computations automatically only after view has appeared

I have a view controller that just shows progress during calculations. I put the method calls
in viewDidLoad but the problem is the view only appears once the calculations are done! How
could I automatically launch the calculations after the view has appeared on screen?
You may use GCD. Here is Raywenderlich tutorial
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
//Calculations
dispatch_async(dispatch_get_main_queue(), ^{
//Update UI must be here
});
});
}
viewDidLoad: triggers when the view is loaded. This is different from when the view is displayed.
Try starting the calculations in the - (void)viewDidAppear:(BOOL)animated callback method on UIViewController instead.
If these calculations take a while, considering running them on a background thread. This will prevent the UI from locking up while the calculations are running. This has the bonus of not only allowing the view to show, but it can be interacted with while the user waits.
[self performSelectorInBackground:#selector(doCalc)
withObject:nil];
From that doCalc method you would call back to the main thread with the result.
[self performSelectorOnMainThread:#selector(didCalcValue:)
withObject:result
waitUntilDone:NO];
As others have correctly pointed out, viewDidAppear let's you know when the view has appeared on screen. *Also, don't forget to call super when you use these event methods.
Example:
// Tells the view controller that its view was added to the view hierarchy.
- (void)viewDidAppear:(BOOL)animated
{
// makes sure it's also called on the superclass
// because your superclass may have it's own code
// needing to be called here
[super viewDidAppear:animated];
// do your calculations here
}
Commonly used UIViewController events:
– (void)viewDidLoad
Called when your view first loads in memory.
– (void)viewDidAppear:
Called after your view has appeared on screen.
– (void)viewWillDisappear:
Called before your view will disappear from the screen.
See the full list on the UIViewController Class Reference page.

viewWillAppear does not run when using addSubView!

I'm stuck! I can't see why viewWillAppear doesn't run in my code but viewDidLoad runs. If I understand it correctly viewDidLoad runs once on the first instance and viewWillAppear runs every time a view is added to the stack of views to display.
I see others have had this issue but some how their solutions of calling viewWillAppear directly causes my app to crash. Other solutions were related to Navigation Controller and pushingView's but thats not what i'm using either! What am I missing?
Thanks in advance for your help! :)
See below:
View Controller #1 - Currently being displayed on screen
-(IBAction)someButtonPressed:(id)sender{
NSLog(#"FirstViewController - someButtonPressed");
SecondViewController *secondViewController = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
secondViewController.myLocation = self.myLocation;
secondViewController.myDatabase = self.myDatabase;
[self.view addSubview:secondViewController.view];
//[secondViewController viewWillAppear:YES];
}
SecondViewController:
- (void)viewWillAppear:(BOOL)animated {
NSLog(#"SecondViewController - viewWillAppear");
[super viewWillAppear:animated];
// updating ivars with data
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
NSLog(#"SecondViewController - viewDidLoad");
[super viewDidLoad];
}
If I understand it correctly viewDidLoad runs once on the first instance and viewWillAppear runs every time a view is added to the stack of views to display.
-viewDidLoad is called every time a UIViewController's view is loaded. That may be many times during a single controller's life as the view may be unloaded to free up memory when it is not visible and reloaded, triggering another call to -viewDidLoad, when needed.
-viewWillAppear: is called when a UIViewController's view becomes visible. However UIKit assumes that UIViewController's views will fill their window. Nesting UIViewControllers' views is an example of abusing UIViewControllers and will result in unexpected behavior. As you have seen.
See About Custom View Controllers in the View Controller Programming Guide for iOS:
Each custom view controller object you create is responsible for managing all of the views in a single view hierarchy. In iPhone applications, the views in a view hierarchy traditionally cover the entire screen, but in iPad applications they may cover only a portion of the screen. The one-to-one correspondence between a view controller and the views in its view hierarchy is the key design consideration. You should not use multiple custom view controllers to manage different portions of the same view hierarchy. Similarly, you should not use a single custom view controller object to manage multiple screens worth of content.
If you wrote a custom UIViewController Container you might have overwritten the following method, which leads to your described behavior.
- (BOOL)shouldAutomaticallyForwardAppearanceMethods{
return NO;
}
In this case you have to manually handle beginAppearanceTransition/endAppearanceTransition.
See Apples View Controller Containment article
viewWillAppear: is called when a view controller is displayed in one of the normal ways (e.g. by selecting a tab in a UITabBarController, by pushing onto a UINavigationController, by being popped back to in a UINavigationController, by being presented with presentModalViewController:animated, by being uncovered after dismissModalViewControllerAnimated:, etc). Just displaying a view with addSubview: does not call the method.
It is possible to correctly call viewWillAppear: manually, but in general it's better to use one of the normal ways mentioned above.
Just try this.. I got it working :)
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(#"SecondViewController - viewWillAppear");
// updating ivars with data
}
When you push view or present a view controller by pushViewController:animated or presentModelViewController:animated:, they will call viewWillAppear:animated:, and else method for you. But if you addSubview: manually, you need to call those method by self.

Invocation of ViewDidLoad after initWithNibName

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.

UITabViewController memory management

I have an app which consists multiple tabs managed by the class derived from UITabBarController (the only reason I subclassed UITabBarConteroller is to handle shake event for all views). 3 views are from subclassed UIViewContentroller class, one view is UINavigationController which shows a table.
In every single controller I have, including my subclassed one, I have this:
(void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
App works great. My question is how is default implementation of UITabBarController handles automatic view unloading on low memory conditions.
Only controller derrived from UINavigationController unload automatically (I see viewDidUnload called). viewDidUnload is NOT called for any other view controllers managed by UITabBarController.
Why?
Thanks
viewDidUnload gets called when your view controller's view is removed and deallocated, and this happens after the VC receives didReceiveMemoryWarning. Check for that method being called first. If it's not being called, something else is going on, but if it is being called, then perhaps your view is over retained or not being removed properly.

iPhone Development - Simulate Memory Warning

Background:
I have a tab bar application. Each tab contains navigation controller allowing the user to transition from one view to the other showing a drill down information of the data (each view is being handled by a view controller and each view controller class has didReceiveMemoryWarning method). Lists are populated by pulling the data from web services.
Problem:
When i use "Hardware > Simulate Memory Warning" option of iPhone Simulator, the didReceiveMemoryWarning method is called for ALL my view controllers - even the one which the user is viewing. I don't want to clear any content which is being used by the active view controller. How can I achieve that?
Which method should have the implementation to reload the data after the data was released because of memory warning? (I see that the view controller classes that contain a table view call viewDidLoad method when user comes back to that view, but if the view contains (say UIWebView) then viewDidLoad method is not called. Why is that?)
Edited (Friday 30 January 2009 - 03:10 PM)
(Note: I'm using Interface builder for creating views, and loadView method is commented out.)
So, when a view controller receives a memory warning message, these are the steps that are carried out:
Following method is called:
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
As a result of call to [super didReceiveMemoryWarning], [self setView:nil] gets automatically called?
If any resources should be cleared, then setView method should be overwritten to clear local resources.
[self setView:nil] is not called if the view is currently active (By default). Right? - I'm really curious which method takes this decision and how?
Can you please confirm. Plus, I was getting an error following this approach but adding myObject = nil after releasing myObject in dealloc method of controller class fixed the issue. Thanks.
This is an old question, but I don't see a proper answer, so here goes:
When a memory warning is received, -didReceiveMemoryWarning gets called in ALL view controllers, whether they are the "current" one or not. The view controllers are simply listening for the memory warning event broadcast.
If the view controller's view isn't being used at the time of the memory warning, the controller will unload it by setting the property to nil. How does it know if the the view is used? By the view's -superview property. If view.superview is nil, the view isn't part of any tree and can be unloaded safely.
Once that happens, the controller's -viewDidUnload gets called. This is the correct place to unload any outlets, and anything that will get re-created in -viewDidLoad.
So what is -didReceiveMemoryWarning for? Your controller might have objects that don't get instanced until accessed. For example, you could have a controller that sometimes needs a big chunk of data from a file, but not always. You could have a property set for it like this:
- (NSData*)bigChunkOfData {
// Get data from our instance variable _data, read from disk if necessary
if (_data == nil) {
_data = [[NSData alloc] initWithContentsOfFile:#"/path/to/data"];
}
return _data;
}
This will read the data from disk this first time, then keep it in an instance variable. Since the _data variable is created on demand, it's safe for us to unloaded it in low-memory situations: it'll just get created again next time we need it.
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
[_data release];
_data = nil; // <-- Very important: don't leave strong references dangling.
}
I do my clean up like this:
-(void)setView:(UIView*)view
{
[super setView:view];
if(view == nil)
{
// Our view has been cleared, therefore we should clean up everything
// we are not currently using
....
setView:nil is called by UIViewController in response to a memory warning, if that view is not currently visible - which is basically what you want to know.
EDITED
In answer to the follow ups:
Correct.
That's what I do, and it works for me.
Correct. The implementation of didReceiveMemoryWarning in UIViewController is what does this. If you don't override didReceiveMemoryWarning, then the base class implementation in UIViewController will be called - if you do override it, obviously you should call:
[super didReceiveMemoryWarning]
To ensure that I dont have to handle this for every single viewcontroller I write.. I have just made a Xcode ViewController template which provides guidelines on which objects to release and when..
more explanation here http://iphone2020.wordpress.com/2010/05/30/efficient-memory-handling-in-uiviewcontroller-part-1/
Hope it finds useful.
In regard to the view management and memory warnings:
UIKit doesn’t only allow navigation back from a view controller, but also allows navigation to other view controllers from existing ones.
In such a case, a new UIViewController will be allocated, and then loaded into view.
The old view controller will go off-screen and becomes inactive, but still owns many objects – some in custom properties and variables and others in the view property/hierarchy.
And so does the new visible view controller, in regard to its view objects.
Due to the limited amount of memory of mobile devices, owning the two sets of objects – one in the off-screen view controller and another in the on-screen view controller – might be too much to handle.
If UIKit deems it necessary, it can reclaim some of the off-screen view controller’s memory, which is not shown anyway; UIKit knows which view controller is on-screen and which is off-screen, as after all, it is the one managing them (when you call presentModalViewController:animated: or dismissModalViewControllerAnimated:).
So, every time it feels pressured, UIKit generates a memory warning, which unloads and releases your off-screen view from the view hierarchy, then call your custom viewDidUnload method for you to do the same for your properties and variables.
UIKit releases self.view automatically, allowing us then to manually release our variables and properties in our viewDidUnload code.
It does so for all off-screen view controllers.
When the system is running out of memory, it fires a didReceiveMemoryWarning.
Off-screen views will be reclaimed and released upon memory warning, but your on-screen view will not get released – it is visible and needed.
In case your class owns a lot of memory, such as caches, images, or the like, didReceiveMemoryWarning is where you should purge them, even if they are on-screen; otherwise, your app might be terminated for glutting system resources.
You need to override this method to make sure you clean up your memory; just remember you call [super didReceiveMemoryWarning];.
An even more elaborate explanation is available here: http://myok12.wordpress.com/2010/11/30/custom-uiviewcontrollers-their-views-and-their-memory-management/
Fortunately, the simulator has a handy function that allows you to put low-memory situations to the test. Put some NSLog() statements in both viewDidLoad and didReceiveMemoryWarning, like this:

- (void)viewDidLoad {
NSLog(#"viewDidLoad");
...
}
- (void)didReceiveMemoryWarning {
NSLog(#"didReceiveMemoryWarning");
}