I was coding today for a small project I have. A very simple iOS application that has a very simple storyboard, two different paradigms for iPhone and iPad, but very, very simple. While coding I was struck by the fact — frequently forgotten — that we tend to ignore the obvious. Ok, here’s the deal: take one minute and think about it. What’s the difference between the two methods ? Because I can bet that sometimes you felt there’s none:
- (void)viewWillAppear:(BOOL)animated { // custom something } - (void)viewDidLoad { // custom something else }
Discussion
Of course, this question leads to a slightly more pragmatic one. Which are the use cases that mandate the use of the viewDidLoad
message in contrast to viewWillAppear
. If you need to check something that happens roughly the same time with your view appearing on the screen, which message/ method you’d use ? Since there is a semantic difference between the two, we’d expect a difference in behaviour. And sure there is. There is a very important difference as one cannot occur without the other. So:
viewDidLoad
is called only when the controller loads the view. This happens when the view controller needs the view for some reason. To display the view is one of these reasons.viewDidLoad
is usually not called a second time unless the view is specifically unloaded at some point in time. We’ll get back to this.viewWillAppear
is called just before the view is displayed. This happens always afterviewDidLoad
and is called every time the view is displayed.
One frequent mistake is to consider that viewDidLoad
is coupled with view’s initialisation. Or better said, each time a view is initialised viewDidLoad
is called and, vice–versa, each time viewDidLoad
is called, a view is initialised. Nope. viewDidLoad
is called (again) only when the view controller needs the view. And it does this only once. Thus, the view can reside initialised long before it is needed for something and it might also reside in the background after been called once. In such situations, viewDidLoad
will not be called.
On the other hand, for the standard view controller lifecycle, every time a view is manipulated in its container and, from user’s point of view, becomes visible (i.e. “is displayed”), the view controller calls viewWillAppear
.
Note: Note that I have mentioned standard view controller lifecycle. When you have a custom one — and this is mostly the case when view are manipulated programmatically — the above does not hold always true. Same is valid for viewDidAppear
, but take care of the precedence and the moment of occurrence (viewWillAppear
is called just before the view is displayed, viewDidAppear
is called just after the view is displayed etc). For the sake of this article, consider a standard, not–messed–with, view controller lifecycle.
Let’s put these two to work and test them. Consider a simple app that displays a list of names in a UITableView
. The application has a tab bar controller which is displayed at the bottom. One tab lists a table view with radio operators (“Operators”). The other tab lists a table view with radio connections (“QSOs”). The default tab (when app starts) is the “QSO” tab:
Each time a row is tapped, in either table view, details for each table cell are displayed in a view that is pushed in by the corresponding view controller. There are two view controllers, one of each tab: the “QSO” view controller and the “Operators” view controller. Nothing fancy. Ok.
Let’s choose “QSO” view controller, conveniently named by me (in my app) “QSOViewController”. Add two NSLog
statements. For viewWillAppear
:
/** * To check when viewWillAppear is triggered * in QSOViewController */ - (void)viewWillAppear:(BOOL)animated { NSLog(@"viewWillAppear");
And for viewDidLoad
:
/** * To check when viewWillAppear is triggered * in QSOViewController */ - (void)viewDidLoad { NSLog(@"viewdidLoad");
When I build and run the application, the following is displayed in the console:
2014-04-10 22:45:45.590 QSO Logbook[1894:60b] viewdidLoad 2014-04-10 22:45:45.603 QSO Logbook[1894:60b] viewWillAppear
Note and remember that viewWillAppear
is called after viewDidLoad
. Quite expected, because the QSO view controller (and its controlled view) is the default when application starts. What happen is: the application creates a QSO view controller that creates a view and loads it; viewDidLoad
is called; then viewWillAppear
is called just before the QSO view (with its table, and data and so on) is displayed.
However, if I toggle between each tab view, from “QSOs” to “Operators”, each time I get back to my QSO view, i.e. each time the QSO view controller displays the QSO view, the viewWillAppear
is called just before QSO view is displayed. But notice, viewDidLoad
is not called anymore. Because, as said, this was already loaded:
2014-04-10 22:45:45.590 QSO Logbook[1894:60b] viewdidLoad 2014-04-10 22:45:45.603 QSO Logbook[1894:60b] viewWillAppear 2014-04-10 22:45:45.613 QSO Logbook[1894:60b] Did become active. 2014-04-10 22:45:45.712 QSO Logbook[1894:1303] refreshed 2014-04-10 22:45:48.108 QSO Logbook[1894:1303] refreshed 2014-04-10 22:45:49.541 QSO Logbook[1894:60b] viewWillAppear 2014-04-10 22:45:52.673 QSO Logbook[1894:60b] viewWillAppear 2014-04-10 22:45:55.231 QSO Logbook[1894:60b] viewWillAppear 2014-04-10 22:45:59.671 QSO Logbook[1894:60b] viewWillAppear 2014-04-10 22:46:08.456 QSO Logbook[1894:60b] viewWillAppear 2014-04-10 22:47:16.655 QSO Logbook[1894:60b] viewWillAppear
Ignore “refreshed” and “Did become active”. Both are there for the sake of another, future, tutorial. 😉
However, if the application is restarted, viewDidLoad
will show again, once per session. As a matter of fact (and fun) the view remains loaded even if the app is sent to background and restored:
2014-04-10 23:07:59.659 QSO Logbook[1946:60b] viewdidLoad 2014-04-10 23:07:59.675 QSO Logbook[1946:60b] viewWillAppear 2014-04-10 23:07:59.684 QSO Logbook[1946:60b] Did become active. 2014-04-10 23:07:59.818 QSO Logbook[1946:1303] refreshed 2014-04-10 23:08:04.057 QSO Logbook[1946:60b] In a moment will resign active. 2014-04-10 23:08:04.059 QSO Logbook[1946:60b] Entered background. 2014-04-10 23:08:07.934 QSO Logbook[1946:60b] Will enter foreground. 2014-04-10 23:08:07.935 QSO Logbook[1946:60b] Did become active.
Just make the following changes inside the application delegate:
- (void)applicationWillResignActive:(UIApplication *)application { NSLog(@"In a moment will resign active."); } - (void)applicationDidEnterBackground:(UIApplication *)application { NSLog(@"Entered background."); } - (void)applicationWillEnterForeground:(UIApplication *)application { NSLog(@"Will enter foreground."); } - (void)applicationDidBecomeActive:(UIApplication *)application { NSLog(@"Did become active."); } - (void)applicationWillTerminate:(UIApplication *)application { NSLog(@"Will terminate."); }
Conclusions (some)
So when and, more importantly, for what to use which ? Well, that should not be very difficult. Anyway, after all this it is important to remember that viewDidLoad
does not equal view initialisation. Same is valid for viewWillAppear
. This is important because these two methods are (frequently) used to check if something happens or exists during run-time, most frequently some variable initialisation (wrong approach anyway !) or other data validation or object initialisation that you might wanna do before a view will be displayed. Remember that viewDidLoad
is (usually) called once per application lifecycle and — unless something terribly wrong does happen — it is never called again. Remember that viewWillAppear
is always called each and every time a certain view is about to be displayed. Thus, if you need some checks to be done each time before a view is about to be displayed, this is the place to do it. But exercise caution. And wisdom. You’d might think twice before checking availability of a certain UI component before the component’s container is displayed, isn’t it ? For example, a wise approach would be to check, first, the availability of the container and if not nil… etc.
There is a big separate discussion about view loading and unloading, situations/ use–cases when viewWillAppear
isn’t called, memory footprint[s] and stuff when things “go terribly wrong”. This is a place where didReceiveMemoryWarning
comes in action, but another time about this. That’s another fascinating soliloquy on one’s path to Apple’s immense wisdom. I might write about it.
Further read
- Overview of
UIViewController
Class Reference UIViewController
Lifecycle (very nice diagram)
comment from a neophyte (me)
Simple stuff: viewDidLoad vs. viewWillAppear
Wasn’t so simple for me. Early on, I was not using viewWillAppear at all. I kept running into problems where I pre-set something and it just didn’t happen.
When I tried – (void)viewWillAppear without “:(BOOL)animated” it did get set either. Of course it would build but code in – (void)viewWillAppear would never run – which confused me even more.
Some of us just have to stumble along 🙂
Hi, Earl. Thanks for the comment. One of the points I tried to make is that a programming paradigm is so filled with traps that the young player can easily become a victim. There are many concepts which are just placed “between the lines” and one has to read very, very carefully the documentation. If there is any. 😉
Regards,
AP