Loaded and Appearing Views
Now that you have two view controllers, the lazy loading of views mentioned earlier becomes more important. When the application launches, the tab bar controller defaults to loading the view of the first view controller in its array, which is the temperature conversion view controller. The map view controller’s view is not needed and will only be needed when (or if) the user taps the tab to see it.
You can test this behavior for yourself. When a view controller finishes loading its view, viewDidLoad() is called, and you can override this method to make it print a message to the console, allowing you to see that it was called.
You are going to add code to both view controllers. However, there is no code currently associated with either view controller, because everything has been configured using the storyboard. Now that you want to add code to the view controllers, you are going to create two view controller subclasses and associate them with their respective interface.
Create a new Swift file (Command-N) and name it MapViewController. In MapViewController.swift, define a UIViewController subclass named MapViewController.
Listing 4.1 Creating a new view controller subclass (MapViewController.swift)
import Foundationimport UIKit class MapViewController: UIViewController { }
Notice that you are importing the UIKit framework instead of the Foundation framework. You briefly learned about frameworks in Chapter 3. Frameworks allow code and resources to be packaged up and shared across multiple applications. You can write frameworks to share across your own applications, or you can write frameworks to share with other developers.
Also, Apple packages many frameworks with iOS. In the code above, you need access to UIViewController, a class defined in the UIKit framework. By importing the framework in the source file, you allow this file to use anything publicly declared within the framework.
With the MapViewController class declared, you can now associate the interface you created in Main.storyboard with this view controller class.
Open Main.storyboard and select the map view controller, either in the document outline or by clicking the yellow circle above the interface (Figure 4.14).
FIGURE 4.14 Selecting the map view controller
Open the identity inspector, which is the fourth tab in the inspector area (Command-Option-4). At the top, find the Custom Class section and change the Class to MapViewController (Figure 4.15).
FIGURE 4.15 Changing the custom class
Refactoring in Xcode
The conversion view controller already has a Swift file that was created for you when you created the project. Currently, that corresponds with the ViewController class defined in ViewController.swift. However, ViewController is not a very descriptive name for a view controller that manages the conversion between Fahrenheit and Celsius. Having descriptive type names allows you to more easily maintain your projects as they grow larger. You are going to give this class a more descriptive name.
Open ViewController.swift and Control-click on ViewController. From the menu that appears, select Refactor → Rename.... Xcode will enter an editing mode where you can see all the places in the project where this type is being used. Change the type’s name to ConversionViewController and notice how the name is updated in three places: the type name, the filename, and the reference to the view controller in the storyboard file (Figure 4.16).
FIGURE 4.16 Renaming in Xcode
When you are done renaming, click Rename to commit the changes. That is it; Xcode makes renaming types seamless, and the same tool can be used for variables and methods.
Now that the ConversionViewController and MapViewController classes are associated with the appropriate view controller on the canvas, you can add code to both ConversionViewController and MapViewController to print to the console when their viewDidLoad() method is called.
In ConversionViewController.swift, update viewDidLoad() to print a statement to the console.
Listing 4.2 Logging ConversionViewController’s view loading (ConversionViewController.swift)
override func viewDidLoad() { super.viewDidLoad() print("ConversionViewController loaded its view.") }
In MapViewController.swift, override the same method.
Listing 4.3 Logging MapViewController’s view loading (MapViewController.swift)
override func viewDidLoad() { super.viewDidLoad() print("MapViewController loaded its view.") }
Build and run the application and check out the console (Figure 4.17). If the console is not visible, open the Debug area from the button in the top-right corner of Xcode or by using the keyboard shortcut, Command-Shift-Y.
FIGURE 4.17 Displaying the console
The console reports that ConversionViewController loaded its view right away. Tap MapViewController’s tab, and the console will report that its view is now loaded. At this point, both views have been loaded, so switching between the tabs now will no longer trigger viewDidLoad(). (Try it and see.)
Accessing subviews
Often, you will want to do some extra initialization or configuration of subviews defined in Interface Builder before they appear to the user. So where can you access a subview? There are two main options, depending on what you need to do. The first option is the viewDidLoad() method that you overrode to spot lazy loading. This method is called after the view controller’s interface file is loaded, at which point all the view controller’s outlets will reference the appropriate objects. The second option is another UIViewController method, viewWillAppear(_:). This method is called just before a view controller’s view is added to the window.
Which should you choose? Override viewDidLoad() if the configuration only needs to be done once during the run of the app. Override viewWillAppear(_:) if you need the configuration to be done each time the view controller’s view appears onscreen.