View controllers have a central role in iOS apps and build the skeleton of every app you make. This is because each screen of the app is represented by a single view controller. Because of this central role they have, they are at the center of a lot of activity and perform many duties during their lifetime.
I already wrote about the role of view controllers inside of the MVC pattern and how you use them to structure the flow of an iOS app. Now we are going to see what actually happens inside of a view controller.
During the life of your app, view controllers get created and disposed of and come on and go off the screen. When these and other events occur, they receive different signals from the operating system to respond to each event. This makes view controllers connected to a lot of other code and classes. For this reason it is helpful have a clear picture of what events happen during the common lifecycle of a view controller.
Let’s first define what I mean by “common”. There are a lot of tasks a view controller can perform and as iOS has developed through the years Apple has added more and more. This again shows the central role of view controllers in iOS. What we are going to see here are the common event view controllers almost inevitably go through. There are many more advanced concepts I will not be covering here. Although these advanced concepts can be helpful in some cases, in most apps you build you can do without them and they are not fundamental to understand how the core of iOS works.
So let’s have a look at the common phases a view controller goes through during its lifetime.
View controller are usually created from storyboards. When this is the case, init(coder:) is the initializer that gets called and you have to override. If provides an NSCoder instance as a parameter, which you need only if you are using iOS serialization APIs. This is not used often, so you can ignore the parameter. If you are curious, serialization transforms an object in a byte stream that you can save on disk or send over the network.
During the initalization phase of a view controller, you usually allocate the resources that the view controller will need during its lifetime. These include model objects or other auxiliary controllers, like network controllers. Previous view controllers might also pass these objects to the current one, so you do not always need to instantiate them in every view controller.
Beware that the view of the view controller has still not been instantiated at this point. If you try to access it through the view property of UIViewController, the loadView() method will be called. This might create unexpected behavior and bugs, so it’s safer not to access the view until later in the lifecycle.
Sometimes you might decide to put your view controller’s interface in a separate nib file instead of a storyboard. This might happen, for example, to work in a larger team where different members need to be change view controller interfaces without affecting the work of others. You also might have a project that was created when storyboards did not exist yet, so every view controller had its own nib file. Keep in mind that if your main storyboard starts getting too big, you can split it into more storyboards. You do not need to move every view controller in a separate nib file.
If you create a view controller from a nib file, this initializer is called instead of init(coder:).
This is the method that creates the view for the view controller. You override this method only in case you want to build the whole interface for the view controller from code. My advice is not to do so unless you have a valid reason.
If you are working with storyboards or nib files you do not have to anything with this method and you can ignore it. Its implementation in UIVIewController loads the interface from the interface file and connects all the outlets and actions for you.
When this method gets called, the view of the view controller has been created and you are sure that all the outlets are in place. It is common to use this method to populate the user interface of the view controller with data before the user sees it. It is also a good place where to start some background activity where you need to have the user interface in place at the end. A common case are network calls that you need to do only once when the screen is loaded. If you need to repeat them to update the data in the view controller, viewDidAppear(_:) is more appropriate to do so.
This method is called only once in the lifetime of a view controller, so you use it for things that need to happen only once. If you need to perform some task every time a view controller comes on screen, then you need the next method.
You override this method for tasks that need you need to repeat every time a view controller comes on screen. Keep in mind that this method can be called multiple times for the same instance of a view controller. Code you need to execute only once should go into an initializer or viewDidLoad(). Usually you use this method to update the user interface with data that might have changed while the view controller was not on the screen. You can also prepare the interface for animations you want to trigger when the view controller appears.
This method gets called after the view controller appears on screen. You can use it to start animations in the user interface, to start playing a video or a sound, or to start collecting data from the network.
iOS devices have a limited amount of memory and power. When the memory starts to fill up, iOS does not use its limited hard disk space to move data out of the memory like a computer does. For this reason you are responsible to keep the memory footprint of your app low. If your app starts using too much memory, iOS will notify it.
Since view controllers perform resource management, these notifications are delivered to them through this method. In this way you can take actions to free some memory. Keep in mind that if you ignore memory warnings and the memory used by your app goes over a certain threshold, iOS will end your app. This will look like a crash to the user and should be avoided.
When you want to present a view controller modally, you call this method. You usually don’t need to override it, its standard implementation takes care of everything.
Similarly to the previous method, you call this method to dismiss a view controller that has been presented modally. The same considerations apply.
Presenting a view controller modally is not the only way to move from a view controller to another one. The most common way is to use a container view controller and transition between view controllers using segues from a storyboard. A commonly used container is the navigation controller. You can connect elements in the user interface of a view controller directly to segues that go to other view controllers. When you do this you do not need to call this method yourself, since the segue is triggered when you interact with the UI. Sometimes, though, you might need to perform a segue after an event not coming from the user interface, for example because of the device rotation or a network callback. In these cases you need to call this method with the identifier of the segue you want to perform. You configure the identifier of a segue in the storyboard.
After a segue is triggered and before the transition between the two involved view controllers happens, this method gets called. You override this method to pass data from a view controller to the next when a segue is triggered. The segue passed as a parameter has references to the destination view controller, so you can access it to pass the data.
Before the transition to the next view controller happens and the origin view controller gets removed from screen, this method gets called. You rarely need to override this method since there are few common tasks that need to be performed at this point, but you might need it.
After a view controller gets removed from the screen, this method gets called. You usually override this method to stop tasks that are should not run while a view controller is not on screen. For example, you can stop listening to notifications, observing other objects properties, monitoring the device sensors or a network call that is not needed anymore.
Like every other object, before a view controller is removed from memory, it gets deinitialized. You usually override deinit() to clean resources that the view controller has allocated that are not freed by ARC. You can also stop tasks you did not stop in the previous method because you wanted to keep them in the background. Beware that a view controller going out of the screen does not mean that it will be deallocated afterwards. Many containers keep view controllers in memory. For example, as you go deeper in a navigation controller, all the previous view controllers stay in memory. A navigation controller releases view controllers only when navigating back up the hierarchy. For this reason, you have to keep in mind that a view controller that is not on the screen still works normally and receives notifications. Sometimes this is desiderable, other times it isn’t, so you need to keep this in mind while developing your app.
These are the most common methods that get called during the lifetime of a view controller. You will likely override or call most of them when creating your custom view controllers in any non trivial app. They are not all needed all the times, but you will need some of them here and some others there.
As I said, since view controllers are the central point of any iOS app there is much more they are requested to do. For example, view controllers take part in the responder chain and handle touch events. They have methods for Auto Layout, view controller transitions, custom view controller containment or to interact more with storyboards. Since iOS 8 they also conform to protocols to build adaptive interfaces and they also have methods to show other view controllers based on size classes. These are all advanced topics that deserve their own articles, so I am not going to list their methods here. If you want, you can find all these in the UIViewController class documentation. The View Controllers documentation page is also helpful.
[referral-form title=”View controllers are only one of the aspect of professional iOS apps. Learn the other ones in my free course” button=”Send me the FREE course”]
Matteo has been developing apps for iOS since 2008. He has been teaching iOS development best practices to hundreds of students since 2015 and he is the developer of Vulcan, a macOS app to generate SwiftUI code. Before that he was a freelance iOS developer for small and big clients, including TomTom, Squla, Siilo, and Layar. Matteo got a master’s degree in computer science and computational logic at the University of Turin. In his spare time he dances and teaches tango.
6 thoughts on “The common lifecycle of a view controller”
Thanks for great article. I’m studying ios with my group people.
I think this article is perfect for our group.
So may i translate this article for my group people?
Hi Matteo ~
Why to start collecting data from the network in viewDidAppear rather than viewWillAppear ?
It’s just a matter of choice. You can start it already in viewWillAppear(_:) if you want. Sometimes that might depend on some factors that are known only after the UI is on screen.
In most cases, it does not make much difference, since these two methods are called in rapid succession.
Thanks for answering!
Good one, you could add things like `viewDidLayoutSubviews` too. Comparing to `viewWillAppear`, it is called every time the controller’s view is updated, rotated, or changed (or more technically, each time its bounds change).