Now that you’re familiar with Xcode and how to create a project, and you’ve explored Swift, the language you’ll use to build apps, you’re ready to start building an app.
In this chapter, you’ll use view controllers and views, the basic building blocks of building any app, to build two example apps:
In the next chapter, we’ll look at integrating user interaction with the distance converter app. In later chapters, we’ll look at techniques for laying out an interface. But first, we need to look a little closer at the view hierarchy.
As mentioned in chapter 1, everything you can see in your app is either a view or contained within a view. Examples of views are labels, images, or plain vanilla—views! Controls such as buttons, date pickers, and switches are types of views, too.
All the views in your app could be represented in a hierarchy—views can contain other views. Right at the top of every view hierarchy of an iOS app is a special view called the window.
The window represents the entire area taken up visually by your app. You’re familiar with the concept of a window from desktop computers.
With enhancements of multitasking in iOS 9, the similarity with windows on desktop computers is even closer, because multiple apps can now be visible on the screen simultaneously. App windows no longer necessarily take up the entire dimensions of the screen. We’ll look more at the implications this has on layout in chapter 6.
Though the app window is also a type of view (subclassing UIView), it doesn’t display any content on its own. Rather, it contains another view, called its subview. Don’t get subviews confused with subclasses—a subview is a view contained in another view, while a subclass is a class that inherits its implementation from another class.
In a simple interface, an app window’s subview could be the root view for a scene. This root view would then contain subviews for every element in the interface. Subviews could be text fields, buttons, images, or other simple views. Subviews can then contain further subviews, and so on.
The distance converter app you’ll build later in the chapter will allow the user to enter a distance in miles or kilometers and perform a conversion. See figure 4.1 for the view hierarchy of the distance converter app.
In the distance converter example, the app window has a subview that’s the root view for the converter scene. The root view covers the available space in the window. Underneath this root view are all the views of the scene—the text fields and labels that make up the app’s interface.
To ensure good code design in iOS, it’s highly recommended you follow the model-view-controller (MVC) design pattern. Objects in your code are conceptually divided into three broad categories: model, view, and controller. Using the MVC pattern keeps your code organized and easily manageable as you maintain or extend your app:
The main scene of the distance converter app will be connected to a main view controller, which will coordinate between the text field views and distance data. The view controller will generate a distance model object with a default value, and use it to update the text field views with the current distance.
When you make the distance converter truly interactive in the next chapter, these text fields will notify the view controller when the user makes a change and the view controller will in turn update the distance model object.
The model in your distance converter app will only be updated by the view controller, but in certain apps it may also be updated by an external source, such as a web service. In these cases, the model should then notify the view controller of this change, so that the view controller can perform any necessary tasks, such as updating the view.
See figure 4.2 for a look at how the MVC pattern works in iOS, in relation to the distance converter app.
As you can see, in iOS, the model and view are both self-contained units. They know nothing about each other, nor do they know about the scene they’re in. The view controller contains references to the model and view objects and is in the middle of the communication between objects.
If the view and model objects don’t have a reference to the view controller, how can they communicate with it? iOS has alternative solutions to solve this dilemma. In the next chapter, we’ll explore three approaches commonly used with view objects: target-action, events, and delegation (also often used with model objects). Later in the book, we’ll look at notifications and bindings, approaches more commonly used with model objects.
Let’s look more closely at the view controller and see it in operation in a sample app.
View controllers have several important responsibilities, such as
As you’ve seen, every scene has a root view, which contains all the views in the scene, and a view controller, which is responsible for managing all the views in the scene. The view controller automatically contains a reference to the scene’s root view. Figure 4.3 shows how the view controller fits into the distance converter scene view hierarchy.
The views themselves are already built to do what they do: the label will display text, and the text field will display the software keyboard and accept user input. But it’s up to the view controller to react to your specific app’s needs.
Most of the time, the default behavior of views in a scene isn’t sufficient. Code needs to be written to customize the behavior of the scene. This custom behavior is performed by the view controller.
For example, in the completed distance converter app, the view controller will respond when the user enters a value in miles or kilometers, convert to the appropriate measurement, and display the result to the user. But how do you create a custom view controller? You can write the custom behaviors of your view controller by subclassing the UI-ViewController class and connecting this class with the scene.
Let’s put the distance converter app aside for the moment—you’ll build that later in the chapter. For now, you’ll build a basic app that displays views in code.
Create a new Single View Application Xcode project following the steps you went through in chapter 1. Call this project “ViewsInCode.” As the name suggests, the Single View Application template sets up an app with one scene created and ready to use.
Follow these steps to find where the class connected to a scene’s view controller is defined:
See figure 4.4 to help you navigate these steps.
Open the view controller subclass now. You can either select ViewController.swift in the Project Navigator or, for convenience, you can click the arrow next to the custom class in the Identity Inspector (see figure 4.5).
When you open the file, you’ll find that the ViewController class is by default prepopulated with two methods, overridden from its superclass UIViewController (see figure 4.6).
During the lifetime of a view controller, it will go through certain life events. At these special times in its life, certain view controller methods will be called. When you subclass UIViewController, you can override these methods to provide custom implementation in these moments.
You’ll see viewDidLoad and didReceiveMemoryWarning in the default ViewController subclass. You can override many more methods, and we’ll look at more shortly. But for now, viewDidLoad is a great place to start, because it’s triggered after the root view, and all of its subviews have loaded.
To prove that you automatically have access to the root view of the scene at this point, let’s change the background color of the view in code to yellow.
The variable in the view controller that references the root view has a name that’s simple enough to remember: view. You could test this out with simple code completion.
view.backgroundColor = UIColor.yellow
Not many apps have one scene. To illustrate, let’s temporarily add a second scene to the ViewsInCode app.
iOS performs the following steps between launching your app and seeing the root view of your initial view controller:
In the end, you’ll have relationships between the app window, initial view controller, and the root view that look like figure 4.10.
To know how to subclass a UIViewController object, you need to know which methods to override and when. To do this, you need to examine the steps a view controller goes through in its life cycle.
Initializing a view controller
As is the custom in any Swift type, the view controller starts off life with an initializer. The initializer doesn’t have access to the root view, so configuring the views in the scene often occurs later in the life cycle.
Loading a view
If the root view isn’t yet loaded, the view controller needs to load it. Wait—how could a root view already be loaded? Well, when one scene navigates to another scene, the originating view controller stays in memory. When returning to the originating view controller, its root view will display but doesn’t need to load again and the viewDidLoad method won’t be called.
The most common and recommended way for a view controller to load its root view is via the storyboard. However, a view controller can also get its root view in other ways. It can load its root view from a nib file (like a storyboard with one scene), or alternatively instantiate it in code by overriding the UIViewController’s loadView method.
After a root view and all of its subviews are loaded, the viewDidLoad method is called. This method is commonly overridden to perform any additional one-off setup that your root view requires. As you did earlier, you could modify the properties of the root view itself. Alternatively, you could modify the properties of its subviews, or instantiate and add new subviews to the root view.
Displaying a view
Whether the root view needed to be loaded or not, the viewWillAppear method will be called before the view displays. This method is commonly overridden if you want to update the root view or its subviews every time you navigate to this scene.
Imagine the main menu scene of a game that displays a top score field. When the user navigates to the game itself and then returns to the main menu, the top score should be updated in case the player beats it! The top score field should then probably be updated in the viewWillAppear method.
The viewDidAppear method is called after the root view is displayed. This method is commonly overridden to initiate processor-intensive work that otherwise could cause sluggishness in presenting the view. This could include starting an animation, playing a sound, or making a network call.
Removing a scene’s root view
Notice in the figure that the UIViewController’s methods for displaying its root view have companion methods for removing its root view.
For example, if your app navigates to a second scene, the root view for the first scene would disappear. Before this view is removed, the viewWillDisappear method is called. After the view is removed, the viewDidDisappear method is called. Override these methods to perform any final tidying up when the view disappears. Perhaps you want to stop a sound file, stop a perpetual animation, remove notification observers, or store a state.
Deinitializing a view controller
When any object is removed from memory in Swift, a special deinit method is called. Implement the view controller’s deinit method if you want to perform any additional cleanup right before this view controller is destroyed.
Releasing memory
One method that didn’t make the life cycle chart is didReceiveMemoryWarning. You might remember seeing this method in the autogenerated custom view controller code. With modern devices, the need to free up memory is unlikely, but if your app does have high memory expectations (for example, perhaps it’s storing many images in a cache), overriding this method is where you could free up that memory.
Now that you know more about view controllers, let’s look at how to use a view controller to manage views. Views can be built up in code or in Interface Builder. Let’s first look at building up views in code.
Open your ViewsInCode project again. You’ve demonstrated you had access to the view controller’s root view by editing its background color in the subclassed view controller’s viewDidLoad method.
Now that you have access to the root view, you can add subviews to it in code. Let’s add a red view that fills the width of the root view, but only half the height (see figure 4.11).
In the ViewController class, define an implicitly unwrapped UIView object called redView above the viewDidLoad method.
var redView:UIView!
To instantiate a UIView, you need to pass in the view’s frame dimensions using a structure type called CGRect. Get the width and height of the root view with view’s bounds property. You can then set its background color to red and add it to the scene’s root view.
redView = UIView(frame: CGRect(x: 0, y: 0, 1 width: view.bounds.width, 1 height: view.bounds.height / 2)) 1 redView.backgroundColor = UIColor.red 2 view.addSubview(redView) 3
Both the frame and bounds of a view refer to a rectangle, defined by a CGRect object that contains the size (width and height) and origin (x and y) of the view. The origin of a CGRect object usually refers to the upper-left corner.
The difference between frame and bounds is that frame is seen from the perspective of the view’s superview coordinate system, and bounds is seen from the perspective of the view’s own coordinate system. As you can see in the view in the example, the size of the bounds is often the same as the size as the frame of a view. The origin, on the other hand, often differs, depending on the perspective.
If you apply any transformations to the rectangle, however, such as scaling or rotation, the size of a view can look different from the perspective of a view’s own coordinate system or its superview’s. Scale a view 50% such as in the example, and the size of its bounds won’t change, but from the perspective of its superview, the size of its frame has shrunk by half.
We’ll explore transformations further in the next chapter.
Well, that’s all fine for basic views, but how about more complex views such as labels? Let’s add a label to the view halfway down, as in figure 4.12.
You can instantiate a label the same way as with a view, but using the UILabel class.
Let’s position the label halfway down the root view with 20-point margins and give it an arbitrary width and height of 20 points.
Don’t worry, I’m not down in the dumps!
Points are how coordinates and distances are measured in iOS and are distinct from the actual pixels in the screen of the device. The intention of points is to have consistency of scale across different devices, especially Retina and non-Retina devices. In the main, the underlying pixels are irrelevant, and you’ll measure distances and coordinates in points.
var label:UILabel!Now, instantiate the label code. Because UILabel subclasses UIView, you can use UIView properties such as backgroundColor. Give the label a temporary background color so you can see it clearly. Display text in the label with UI--Label’s text property, and use UILabel’s font property to adjust the font size.
label = UILabel(frame: CGRect(x: 20, y: self.view.bounds.height / 2, width: 20, height: 20)) label.backgroundColor = UIColor.orange view.addSubview(label) label.text = "Hello World" label.font = label.font.withSize(40)
label.sizeToFit()
Success! You can remove the orange background now if you like, and you should now see something similar to figure 4.12.
If you’d like to compare, you can check out my project at https://github.com/iOSAppDevelopmentwithSwiftinAction/ViewsInCode.git (1.DisplayingViews branch).
You can close the ViewsInCode project; we’ll come back to it later. But now, let’s use Interface Builder to manage views in a scene in the storyboard.
In the remainder of this chapter, you’ll build the interface for the distance converter app that we looked at earlier in this chapter. In the next chapter, this app will convert distances the user enters, but for now you’ll specify a miles distance in code for the app to convert to kilometers and display in the text field (see figure 4.13).
This time, we’ll explore building an interface using Apple’s visual tool for building interfaces, Interface Builder.
If you want to skip the initial setup, you can download it from https://github.com/iOSAppDevelopmentwithSwiftinAction/DistanceConverter.git (1.InitialSetup).
When you modify the text of a label or change its font size, you might find that it’s no longer big enough to contain its content, indicated by an ellipsis (...). You can resize a view to its content by selecting a view, and then selecting Editor > Size to Fit Content. Frustratingly, you might find after running the app that Xcode has slightly misjudged the new size, and you’ll need to add a few extra pixels in Interface Builder. You can do this by dragging the width handle or adjusting the width in the Size Inspector. After resizing, you might also find you need to reposition the view.
You have alternative approaches to creating a file. You could also right-click on the group in the Project Navigator where you want to create the file, select New File, and select the appropriate template. Alternatively, you could find the file template you want in the File Template library in the library area, and drag it where you want it in the Project Navigator. Too many options!
var distance = Distance(miles: 1000)
If you’d like to download the project at this point, you can check it out at https://github.com/iOSAppDevelopmentwith-Swift-inAction/DistanceConverter.git (1.InitialSetup).
Now that you have a distance object, the challenge is to display its miles property in the miles text field and its km property in the kilometers text field. As you saw earlier in the chapter, following the MVC pattern, it’s the view controller’s job to update text field views from the model.
For the view controller to update the text field views, it will need a way of referencing them in code.
The easiest way to get a reference to a view in the storyboard in your custom view controller class is to use what’s called an outlet. An outlet is a variable in your code that’s connected behind the scenes with a view in Interface Builder. Let’s set up outlets for the two text fields now.
Open the main storyboard. To set up your outlets, you’ll first need to open the Assistant Editor.
Opening the Assistant Editor splits the editor area into two editor panes, most commonly with the standard editor on the left and the Assistant Editor on the right. The two editor panes could be the same type of editor, such as two Swift files, or they could be different types, such as the storyboard on the left and a related Swift file on the right.
Open the Assistant Editor by selecting the Assistant Editor selector at the top right—that’s the button in the middle of the three editor selectors that looks like two rings.
When you want to close the Assistant Editor, leaving only the standard editor open, select the editor selector on the left that looks like a paragraph of text.
As you’ve seen, if you select a file in the Project Navigator, the standard editor pane will open that file in the appropriate editor. How do you open a file in the Assistant Editor pane?
An alternative way to open a file in either editor pane is by using the jump bars at the top of the editor panes. The jump bars display a hierarchical path of where what you’re currently editing fits into the project. The hierarchy of the jump bars spreads on the left from the project itself, to your current location in the file on the right. Use the jump bars to jump to a different file or location within a file.
In the Assistant Editor, the left of the jump bar gives you additional modes for navigating to a file. The Manual mode gives you the same hierarchy you’ve seen in the standard editor’s jump bar. But the real magic of the Assistant Editor lies in other automated modes that open files related to your selection in the standard editor. If you have Interface Builder open in the standard editor, for example, the Automatic mode becomes available and automatically opens the source file for the object you select in Interface Builder.
If, for example, you select the view controller in the storyboard, your custom ViewController class will automatically open in the Assistant Editor pane, if you have Automatic mode selected.
Now, you should have the storyboard open in the standard editor, and the Assistant Editor open to the view controller source file.
You should see a line of code appear in your code defining the outlet:
@IBOutlet weak var milesTextField: UITextField!
An @ symbol indicates an attribute that provides more information to the compiler about a variable or type declaration. Attributes that begin with IB indicate a possible connection in Interface Builder.
You might notice several interesting aspects of this outlet property:
Automatic Reference Counting (ARC) is Apple’s approach to automatically removing objects from memory that are no longer being referenced. All instance variables default to be strong. While at least one strong reference to an object exists, it won’t be deallocated from memory. A weak reference to an object, on the other hand, won’t prevent an object from being deallocated from memory. Why then, doesn’t a weak IBOutlet variable get deallocated from memory? IBOutlet variables usually refer to a view in the view hierarchy, which are automatically strongly referenced in a view’s subviews array. While the variable remains in the view hierarchy of a view controller in memory, it will not be deallocated from memory.
Now that you have an outlet for milesTextField, you can edit its properties, the way you did earlier when you created views in code.
milesTextField.text = "(distance.miles)"
kmTextField.text = "(distance.km)"
The value for miles that you used to instantiate your distance object will be converted to kilometers and displayed in the relevant text fields.
Congratulations, you can now make connections in code to views you set up in the storyboard!
If you’d like to compare your code with mine, you can check out the next branch at https://github.com/iOSAppDevelopmentwithSwiftinAction/DistanceConverter.git (2.ConvertDistanceFromCode).
In the next chapter, you’ll make this distance conversion useful by including user interaction, using the special abilities of types of views called controls.
In this chapter, you learned the following: