Welcome to Day 24 of the 31 Days of iOS. Yesterday, we went over how to do processing in a background thread to prevent hanging up the UI thread. Today we’re going to talk about the view lifecycle. By that we are referring to the different events and methods that are automatically fired on your view controller in the normal course of using the app. We’ll follow up tomorrow by talking about Application life cycle events which differ from the ones on the view. We’ll be starting with a brand new project, but if you’d like to follow along with the completed code, you can access it here.
Creating our project
Open up Xcode and choose File, New, Project. We’ll use a Single View Application and name it DayTwentyFour:
Some of the life cycle events depend upon going from one view to another, so we’re going to alter our UI a bit to facilitate those events showing up. Open up MainStoryboard.storyboard. First, select the view that is already on your storyboard, and go to the Editor menu and choose Embed In and select Navigation Controller. We’ll use this to help make it easier to move to a new view and back. Next, drag a View Controller, from the element selector in the bottom right, onto the storyboard. When you’re done, the storyboard should look like this (zoomed out):
Next, drag a round rect button from the element selector onto the original View Controller (the one in the middle in the image above). Next, control + click and drag from the button to the View Controller on the right and let go. In the popup that comes up, select Push. When you’re done, the right two views should look like this:
Our UI is done now. If you run your app, you should be able to tap the Show View button and the other view will appear. You should then be able to tap a button on the top left of the view to return to our first view. With that working, we can start looking at the life cycle events.
Initial view controller life cycle events
Open up ViewController.m and you’ll see a few methods that are added by default: viewDidLoad and didReceiveMemoryWarning. After adding a log statement, our viewDidLoad method will look like this by default:
Notice the first call is to [super viewDidLoad]. This means we’re calling the original ViewController classes implementation of the viewDidLoad method. We do this because the root class does a lot of setup that we want to use but don’t want to code ourselves. After that is done, we can perform whatever setup we want. This isn’t the first method called in the life cycle of our view but is the only one that is always called and is put in the class by default. As it says in the comment, additional setup should be performed here. However, if you need to do anything that is determined by screen geometry, frame size, UI element size, etc, you’ll want to wait until a later method.
The second method present is didReceiveMemoryWarning. This method also just calls the super class’ method of the same name. This method will only be called when your app is using up too much memory and is an indicator to you that you should release memory if possible. With Automatic Reference Counting (ARC) you typically aren’t handling deallocating memory so you may think you don’t need to worry about this method or what it’s being called means. However, you still have control over how much memory you are allocating and where it’s being used. I would advise you to keep an eye out for this method being called and, if you do see it being called, refactoring or redesigning your code.
There are a few different initialization methods that can be called when our view controller is first created: initWithNibName, initWithCoder, and init. In our app today, we’ll only see the method initWithCoder being called. This method is called as a result of us loading our view from a storyboard. If we were instead using Nib files (which was standard prior to storyboards and is still possible if you want) the initWithNibName method would be called. Lastly, if we were instantiating our view controller class on our own without using the other two methods, init would be called (as the standard initializer for classes). I’ve gone ahead and implemented each of these methods (which like the two mentioned above call their super method of the same name) and added a log statement so we can see a complete flow in just a few minutes. There is one other method which is sort of like an init method that we aren’t going to use today but will talk about: loadView. This method is called automatically on your view (and is normally handled by the super class). If you were not going to use a view from the storyboard or a nib file (that is you wanted to create your whole UI programmatically) you could override this method and set your view up here. Since we want to handle things the easy way and use storyboards, we won’t use the loadView method.
viewDid and viewWill
The next set of methods all either start with viewDid or viewWill. These methods are: viewWillAppear, viewWillDisappear, viewWillLayoutSubviews, viewDidAppear, viewDidDisappear, and viewDidLayoutSubviews. After the viewDidLoad method is called, viewWillAppear is called. When this method is called, the geometry of your view has been figured out and you’re safe to do any processing that is dependent upon that. It’s important to remember that in the life of a view controller, the viewDidLoad method is only called once but viewWillAppear might be called many times. If you wanted data you were presenting to the user to be updated each time the user returned or went to your view, you might want to do that updating in the WillAppear method instead of DidLoad. If it’s something that only needs to happen once, it’s probably best left in DidLoad.
After that, viewWillLayoutSubviews is called followed by viewDidLayoutSubviews and viewDidAppear. If you tap the button on our view (which will cause the second view we created to show up) you’ll then see the viewWillDisappear and viewDidDisappear methods be called. This happens because your view isn’t being completely deallocated when you push another view on top of the current one. Instead it is still stored in memory so it can be quickly restored to the screen when the user goes back. One example of something you might want to do on WillDisappear is to save to storage any text data entered into view. That way, if the user leaves your app before returning to the view, you can load that text data back into the view to give the user a seamless experience and not require them to retype it.
Finally, when the user does go back, we again see the viewWillAppear, viewWillLayoutSubviews, viewDidLayoutSubviews, and viewDidAppear methods being called.
Let’s take a less wordy look at the flow of our app:
This is the all of the events that are logged when our view first appears. If we look at just the event flow that happens when we tap the Show view button and then return, we get this:
This makes it a little easier to see what order these events are called in.
What about closing the app?
Before we finish today, hit the home button when your application is running. You’ll notice that no events are triggered. This is because leaving the app doesn’t trigger any view life cycle events. Instead, it will fire off application life cycle events. We’ll talk more about these tomorrow.
Today we looked at the life cycle of a view controller in an iOS application. By default when you create a new view controller, two methods that are part of the life cycle are present. However, there are many more you can implement depending on when you need to perform what actions. One event type I didn’t talk about today which could be thought of as life cycle methods is the calls for orientation changes. You can read more about them in Day 16’s article on device orientation. You can download the completed code from today’s walkthrough here.