Building Multiscreen Apps - Introducing iOS 8 (2015)

Introducing iOS 8 (2015)

Chapter 5. Building Multiscreen Apps

In this chapter, you will learn how to manage multiple screens inside one app. You will also learn how to make a scrolling list of items. Most apps have multiple screens and at least one scrolling list. This chapter will build on your knowledge base and bring you closer to releasing an app to the App Store.

View Controllers

View controllers are the logic portion of the Model-View-Controller, or MVC, paradigm. See Chapter 1 for a quick review of MVC. It won’t surprise you to learn that a view controller is a controller that controls the view. Don’t overthink it. The view displays information and receives input from the user, but it does not make decisions. Those decisions are made inside the view controller.

UIViewController

Apple highly encourages the MVC approach to development. They recommend it so much that they created their own controller called the UIViewController. UIViewController is part of UIKit. UIKit is a set of classes provided to create interface elements. You will see the prefix UI before any class that is part of UIKit.

UIViewController has been configured to handle much of the heavy lifting for you. It comes with a view property out of the box. The view property is connected to a view file, in most cases, a storyboard file.

UIViewController also has prewritten methods ready for you. Common events like the view loading on the screen or the view disappearing from the screen are already written for you. These methods can be filled with your own custom code.

Overall, UIViewController provides methods and properties you would have needed to write anyway. This makes UIViewController a perfect parent class to use as a template for your own custom class.

For example, it is common to subclass UIViewController and add some code to run when the view is first loaded on to the screen. This is handled in viewDidLoad:

class mySubController: UIViewController{

override func viewDidLoad() {

super.viewDidLoad()

// Do any addition setup after loading the view

}

}

Take a look at the viewDidLoad() method inside of the mySubController class. The override keyword is used to tell Xcode that you would like to add to or change UIViewController’s default viewDidLoad method. The func keyword declares the method. The termviewDidLoad is the method name you would like to override from UIViewController. The viewDidLoad method does not take any parameters, so the parentheses are left blank. Finally, you add a set of open and closed braces. These braces are used to determine the beginning and end of the method.

Whenever you are overriding a method, the first line of code should make a call to super. super is a keyword used to reference the parent class of the current class. In this case, the parent class is UIViewController. This call to super is important because without it theviewDidLoad() method call would do absolutely nothing before your code was executed, just like if you were going to build a house but did not first lay the foundation. In this case, a call to super.viewDidLoad() sets the foundation. Then you can add your own custom code after the foundation is complete:

class mySubController: UIViewController{

override func viewDidLoad() {

super.viewDidLoad()

// Do any addition setup after loading the view

println("The View Has Loaded")

}

}

The println method call will print the message “The View Has Loaded” to the Debugger when viewDidLoad executes. It’s a good habit to remember each UIViewController in your app will have a dedicated view and controller.

UINavigationController

Up until now, each of the apps you created has only one view. However, many of the most popular apps on iOS use multiple views and transition from left to right. For example, the Mail app will slide a new view onto the screen when you tap an email. The Mail app also provides a back button to go back as well. This left-to-right or transition behavior is created by a navigation controller.

A UINavigationController can hold many UIViewControllers in an array. Remember, an array is a collection type; it holds many items in a specific order. The navigation controller uses a sorting mechanism called a stack. A stack sorts items in a first-in, last-out order. A stack is similar to boarding an airplane. If you are first to board the plane you go to the very back and then when it is time to leave you are the last to get out. This is a first-in, last-out sorting paradigm at work.

Imagine you are the first to board a plane. You walk to the very back and take the window seat. Then a male passenger boards and takes the middle seat next to you. Then a female passenger boards and takes the aisle seat. When it comes time to exit the plane, the female passenger must exit first. Then the male passenger exits, and then finally you can exit the plane. A stack loads and unloads in reverse order. It loads from bottom to top and then unloads from top to bottom.

To add a view controller to the navigation controller, you push the view controller on top of the navigation controller. A push is equivalent to the first person boarding an airplane. The item goes as far toward the bottom as possible, but does not pass the items inserted before it. The topmost view controller in the stack will be displayed to the user. To show another view controller on top of the current one, push on the new view controller.

To remove the current view from the display, a pop is executed on the navigation controller. A pop removes the topmost item from the stack. In this case, it removes the current view and displays the previous view. A pop is equivalent to the first-class passengers leaving the plane: last in, first out.

When a view is displayed inside of a navigation controller, a navigation bar is added to the top of the current view. This navigation bar has three main sections: the left UIBarButtonItem, the titleView, and the right UIBarButtonItem. A UIBarButtonItem acts like a typical button, but it lives inside a UINavigationBar. UINavigationBar is the class used to create the navigation bar at the top of a navigation controller. The leftBarButtonItem will automatically become a back button when another view is shown in the navigation controller. The titleView will display the value from the UIViewController’s title property. The key to remember here is that the title is defined by the view controller, but shown by the navigation controller. Setting the title to a view controller is simple:

title = "Countries"

Each view controller has a title property, because UIViewController defines the title property. The back button will also use the title from the previous view controller instead of the word “Back.”

Finally, the right UIBarButtonItem is optional; this is a location where a primary or secondary action could be placed. For example, a settings button could be placed in the upper-right corner.

Table View

A scrolling list of items is one of the most common interfaces used in iOS Apps. For example, the Settings app has a scrolling list of categories and when the user taps on an item, another screen with more details is shown. This effect is achieved by the combination of a navigation controller and table views. The navigation controller controls which view is currently displayed, and the table view displays the scrolling list of items.

Table views are made up of a few key parts. Each item or row in the list is called a cell. A cell is a view used to display an individual row in the table view. The table view has many cells, and they are grouped into sections. Sections are used to separate rows into groups. The Settings app uses sections to group categories like Notification Center, Control Center, and Do Not Disturb. Each table view has a header and footer. The header is a view that sits above the cells, while the footer sits at the bottom after the cells.

Finally, a table view is set to one of two styles. The default style, Plain, lists each row one after another with no separation. The other style, Grouped, will separate rows by their section and place a divider between each group.

Delegation

Most sports websites and apps provide a service that sends alerts to your phone or email whenever your favorite team scores. For example, you might receive a buzz on your phone whenever a San Francisco Giants player hits a home run. These updates are only shown when a particular event happens, and the event could happen at any time. Since there is no scheduled time for these events, an update is sent to the subscriber whenever the event occurs.

This same notification concept is used to alert a controller when an event has happened inside the app. The process of subscribing to or receiving notifications for a particular event is called delegation. Delegation specifies a delegate to receive all notifications. However, the delegate doesn’t just receive the notifications; in most cases, the delegate must respond to them as well.

For example, if you are the manager of a company, you might tell your security officer to monitor the break room and knock on your door whenever there are more than three people in the break room at a time. The knock would serve as a notification, and you could choose how you would like to handle the notification each time you are notified.

When a table view is first being created, there are a series of questions that must be answered. The table view sets a delegate to receive the questions and provide answers. For example, the table view will ask the delegate how many rows should be created. The delegate could respond with a specific number like 20 or even an integer variable.

Instead of answering questions, you override a specific method for each piece of information. For example, to provide the number of rows, you override the numberOfRowsInSection method:

override func tableView(tableView: UITableView,

numberOfRowsInSection section: Int) -> Int {

//Return the number of rows in the section

return 10

}

Don’t worry about understanding every part of this method; just focus on the code inside the method. In this case, the numberOfRowsInSection method returns an integer. Set the return to 10, and 10 rows will be created inside the table view. This is just one of many questions that must be answered to create a table view.

The full list of questions are wrapped up inside a protocol. A protocol is a specific way of doing something. For example, the protocol at the airport is to check your baggage, go through security, and then board the plane. During each of these steps, you may be asked to provide some input like your destination. The key is that you must agree to complete all the required steps in the protocol. In development, this is called conforming. Conforming is agreeing that you will respond to all of the required methods in a protocol.

UITableViewController

UIViewController is a view controller class created by Apple to help set up the typical methods and properties most view controllers need. In the same light, UITableViewController handles the heavy lifting for creating a view controller with a table view.UITableViewController will automatically create a table view and set it to a property named tableView. It will also specify itself as the delegate and stub out all of the required delegate methods. A stubbed-out method is a term used to describe a method that is declared with nothing inside of it yet. Finally, the UITableViewController will take care of highlighting and unhighlighting rows when they are tapped. It makes creating and managing table views a breeze.

UITableViewDataSource

The delegate protocol that holds the three required methods for UITableView is called UITableViewDataSource. This protocol gathers information for the number of sections, the number of rows in each section, and the cells to be displayed.

The first required method is numberOfSectionsInTableView(_:). This method asks for an integer representing the number of sections in the table view. Overriding this method is straightforward. For example, the method will look like this inside the UITableViewController:

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {

//#warning Potentially incomplete method implementation.

//return the number of sections.

return 0

}

Notice the return line is currently set to 0; this means there will be zero sections in the table view. Apple has added a warning comment to remind you to override this method. If there are zero sections, then no rows will be shown. It is important to remember that sections hold cells; without a section, no cells will be shown:

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {

//return the number of sections.

return 1

}

The next required method is tableView(_:numberOfRowsInSection:). This method asks for an integer representing the total number of rows for a given section:

override func tableView(tableView: UITableView,

numberOfRowsInSection section: Int) -> Int {

//#warning Incomplete method implementation

// Return the number of rows in the section

return 0

}

Notice the return line is currently set to 0; this means there will be zero rows in each section of the table view. Apple has added a warning to remind you to override this method:

override func tableView(tableView: UITableView,

numberOfRowsInSection section: Int) -> Int {

//#warning Incomplete method implementation

// Return the number of rows in the section

return 5

}

Finally, the UITableViewDataSource protocol requires that you respond to tableView(:cellForRowAtIndexPath:). This method provides the cell for each row in the table view. The method provides a parameter named indexPath. The indexPath parameter is anNSIndexPath. NSIndexPath has two properties. The section property is the index of the current section. The row property is the index of the current row. Remember counting starts at 0 with all indexes. This means:

§ The first row in the first section would have an NSIndexPath of 0,0.

§ The fourth row in the first section would have an NSIndexPath of 0,3.

§ The first row in the third section would have an NSIndexPath of 2,0.

The row and section property are available via dot notation. For example:

var currentRow = indexPath.row

var currentSection = indexPath.section

The tableView(:cellForRowAtIndexPath:) can be daunting, but do not be worried, because it will all make sense after a little while. Take a look at the method provided by UITableViewController. Just focus on the inside of the method for now. Remember, don’t be afraid to make mistakes:

override func tableView(tableView: UITableView,

cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

let cell =

tableView.dequeueReusableCellWithIdentifier

("Cell", forIndexPath: indexPath) as UITableViewCell

return cell

}

Cell reuse

The first line creates a constant named cell. cell is set equal to one of the table view’s dequeued cells. A dequeued cell is a cell that is no longer being used. Just like the real world, it’s important to recycle resources if possible. When there is a table view with more rows than the display can show, the other rows are not actually created. Instead, when a row rolls off the top or bottom of the display, it is placed in a recycling bin. Then before another cell is created, the recycling bin is checked to see if a recycled cell is available. Because cells can be reused, it is important to clear their contents before reusing them.

The table view is accessed by using the tableView variable. Then the dequeueReusableCellWithIdentifier method will grab a recycled cell or create a new one if needed. The dequeueReusableCellWithIdentifier method accepts two parameters: identifier, a String to classify a special type of cell, and forIndexPath, an NSIndexPath specifying the location.

The cellForRowAtIndexPath method starts with the override keyword, because this method was inherited from UITableViewController. The cellForRowAtIndexPath method will be customized outside of its original behavior. The func keyword is used to declare the method. The first tableView is part of the method name. The parentheses hold the parameters for the method. The first parameter is named tableView and is a UITableView. The cellForRowAtIndexPath is the method name. The next parameter is an NSIndexPath named indexPath. Finally, the method will return a UITableViewCell.

Once you have a cell, you can set the properties of the cell to display your data.

There are five main properties to each UITableViewCell:

textLabel

UILabel displays the main message text.

detailTextLabel

UILabel displays a subtitle; this label is not always shown.

imageView

UIImageView displays UIImage on the left side of the cell.

accessoryView

Displays disclosure icons if applicable.

contentView

UIView, blank canvas that holds all the elements.

UITableViewCells have four different styles; depending on the style, each of the five properties may be positioned differently or hidden completely:

Default

Left-aligned text label, optional imageView, and no detailTextLabel.

Value1

Left-side label with black text; it also has a right-side label that has smaller blue text and is right-aligned.

Value2

Left-side label that is right-aligned and contains blue text; on the right side is a left-aligned label with black text.

Subtitle

Left-side left-aligned label with black text; it also has a lower left-side left-aligned label with smaller gray text.

In this chapter, you learned how to manage multiple screens inside one app. You also learned how to make a scrolling list of items. Your knowledge base is growing. Now it is time to put your knowledge to the test. Keep up the momentum and get started on the Passport exercise.

Exercise: Passport

The exercise in this chapter is called Passport. Passport is a simple application that displays a person’s name, birthday, nationality, and photo (Figure 5-1). Passport will also show a list of countries to which you have traveled. This project will be used in future chapters; be sure to follow along closely.

Passport app

Figure 5-1. Passport app

Open Xcode from your Dock and select File→New→Project (Figure 5-2).

New project

Figure 5-2. New project

Select Single View Application and click Next (Figure 5-3).

Project template dialog

Figure 5-3. Project template dialog

Name the product Passport, verify the language is set to Swift, and set the Devices drop-down menu to iPhone (Figure 5-4). Click Next.

Project details

Figure 5-4. Project details

Save the project inside the Programming folder (Figure 5-5).

Save project

Figure 5-5. Save project

The project details will be displayed (Figure 5-6). Deselect the Landscape Left and Landscape Right orientations. Then open Main.storyboard (Figure 5-7).

Project details

Figure 5-6. Project details

Main.storyboard

Figure 5-7. Main.storyboard

Look at the Inspector on the right side of your screen. Show the File Inspector by clicking on the first icon in the upper toolbar. The icon looks like a small piece of paper with its corner bent.

Scroll down to the Interface Builder Document section and deselect Use Auto Layout (Figures 5-8 and 5-9). A dialog box will appear; select iPhone and then Disable Size Classes.

Disable Auto Layout

Figure 5-8. Disable Auto Layout

Interface without Auto Layout

Figure 5-9. Interface without Auto Layout

Select the interface, then from the top menu bar select Editor→Embed In→Navigation Controller.

A new scene will be added to the Storyboard, Navigation Controller Scene (Figure 5-10). A scene represents another screen or interface inside your app. The Navigation Controller Scene is connected to the original View Controller Scene. This connection declares the View Controller Scene will be the first view controller shown inside the navigation controller. Then open the Document Outline by clicking the small box in the bottom left of the Storyboard Editor. The Document Outline shows a hierarchy of all the elements in the storyboard file. Then hide the Project Navigator (Figure 5-11).

New navigation controller

Figure 5-10. New navigation controller

Hide Project Navigator

Figure 5-11. Hide Project Navigator

At the top of the View Controller Scene is a light gray box; this is the navigation bar. Double-click it, type Passport, and then press Return.

In the bottom half of the Inspector, click the Object Library search box and type ImageView (Figure 5-12).

Image View in Object Library

Figure 5-12. Image View in Object Library

Drag an Image View onto the top half of the view controller’s view. Then open the Size Inspector; its icon looks like a ruler, and it is the second from the right (Figure 5-13).

Size Inspector

Figure 5-13. Size Inspector

Enter 48 for X, 70 for Y, 225 for Width, and 225 for Height. Then deselect the two crossing arrows inside the Autoresizing box (Figure 5-14).

Repositioned Image View

Figure 5-14. Repositioned Image View

Drag out a Label and position it under the Image View’s bottom-left corner. Place two more Labels under this Label. Select all three Labels and open the Attributes Inspector. The Attributes Inspector is the fourth icon from the left. Next to the font input box is a small box with a T inside it. Click this box, and a pop-up menu will appear. Click the font drop-down menu and change it to System Bold (Figure 5-15). Click Done.

Bold font

Figure 5-15. Bold font

The left-side labels will now be truncated; drag the sizing handle on the right side of each Label until it is at least 100 pts wide. Then change the Labels’ text by double-clicking each Label. Change the Labels to Name, Birthday, and Nationality (Figure 5-16).

Right-side Labels

Figure 5-16. Right-side Labels

Then drag out three more Labels and place them in the bottom-right corner of the Image View. These right-side Labels should align with the left-side Labels. Then select all the right-side Labels and open the Attributes Inspector. Next to the Alignment property, click the third button. This will right-align the text inside the Labels. Drag the left-sizing handle for each new Label until they are all at least 100 pts wide. Change the right-side labels to match your name, birthday, and nationality.

Download an image of yourself from the Web. Next select File→Add Files to Passport (Figure 5-17). Browse to a photo of yourself. Verify that “Copy Items if needed” is selected, select the image file, and click Add. Select the Image View and then click the Image drop-down in the Attributes Inspector. Select the newly added photo (Figure 5-18).

Add file to Passport

Figure 5-17. Add file to Passport

Newly added image

Figure 5-18. Newly added image

The photo may look stretched or skewed. To fix this, click the Mode drop-down menu inside the Attributes Inspector. Then select Aspect Fill; this will ensure that the aspect ratio of the image is not changed, and the image will fill the inside of the Image View. Experiment with the other modes to see what works best.

Drag a Button from the Object Library and position it under the personal details. Double-click the button and name it Show Countries (Figure 5-19). When tapped, this button should show a list of visited countries.

Show Countries button

Figure 5-19. Show Countries button

A list of countries will be shown when the user taps the Show Countries button. Lists are best handled by table views. Apple has provided a controller designed especially for working with table views, called UITableViewController. Drag a Table View Controller out from the Object Library. Place it to the right of the Passport Scene.

Next, select the Show Countries button and Control-drag from the button to the table view controller (Figure 5-20).

Control-drag connection

Figure 5-20. Control-drag connection

A pop-up menu will appear (Figure 5-21). Select Push from the menu and notice the new line connecting the Passport Scene and the Table View Controller Scene.

Connection pop-up

Figure 5-21. Connection pop-up

Also, notice the Table View Controller Scene now has a navigation bar. Double-click the navigation bar and type "Countries Visited".

Select the white bar just under the words “Prototype Cells.” In the Attributes Inspector, type reuseIdentifier into the Identifier input box (Figure 5-22).

Attributes Inspector for Table View Cell

Figure 5-22. Attributes Inspector for Table View Cell

The storyboard portion of the app is nearly complete; now create the table view controller file. Select File→New→File from the top menu bar (Figure 5-23). Verify Source is selected under the iOS section and Cocoa Touch Class is selected; then click Next.

New file

Figure 5-23. New file

It is now time to name the new class that will act as the table view’s controller (Figure 5-24). But first, inside the “Subclass of:” input box, select UITableViewController. This will make the new class a subclass of UITableViewController. UITableViewController does most of the heavy lifting for you. Now in the class input box, type CountriesTableViewController. Leave “Also create .xib” unselected and verify the language is set to Swift.

Name the new class

Figure 5-24. Name the new class

The next dialog will ask where you would like to save the new controller file. The current project directory should already be selected. Verify the Passport folder is at the top and click Create (Figure 5-25).

Save new file

Figure 5-25. Save new file

The new CountriesTableViewController.swift file will automatically be opened inside of Xcode.

Then scroll down and highlight all of the green code at the bottom of the file and delete it. Be sure not to delete the closing brace at the very bottom of the file. Then highlight and delete viewDidLoad and didReceiveMemoryWarning as well. Then hide the Inspector and open the Project Navigator.

Place your cursor below the line that reads class CountriesTableViewController: UITableViewController. This is where properties are declared. The countries visited will be stored in an array. This array will be declared as a property, so any method will have access to it. To declare the array, type the following:

var countries = ["Italy","Norway","England"]

The numberOfSectionsInTableView method specifies the number of sections to be shown in the table view. Change the 0 to a 1. Remove the line that reads //#warning Potentially incomplete method implementation:

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {

//Return the number of sections.

return 1

}

Below the numberOfSectionsInTableView method is the numberOfRowsInSection method. This method specifies the number of rows to be shown in the table view. Change the 0 to a 3. Remove the line that reads //#warning Potentially incomplete method implementation:

override func tableView(tableView: UITableView, numberOfRowsInSection section:

Int) -> Int {

//Return the number of sections.

return 3

}

Place your cursor just below the two methods and type tableView. Autocomplete will appear and begin to assist you as you type. Highlight the line that reads:

tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath)

-> UITableViewCell

Then press Return and erase the code placeholder.

The cellForRowAtIndexPath method is called for each row inside the tableView. It creates the UITableViewCell and assigns text for each cell. Add the following line into the new method:

let cell = tableView.dequeueReusableCellWithIdentifier

("reuseIdentifier", forIndexPath: indexPath) as UITableViewCell

This line creates a constant called cell and sets it equal to a recycled cell from the tableView. The method checks if there are recycled cells available with the reuseIdentifier identifier. If there is a cell available, it sets cell to the recycled cell; otherwise, it creates a new cell. Next, add the following line of code:

var country = countries[indexPath.row]

This line of code sets a country name to the country variable. The cellForRowAtIndexPath method is called for each row in the table view. Each time it is called, the indexPath variable is updated to provide the section and row number. For example, the first timecellForRowAtIndexPath is called, it is for the first row in the first section, then the second row in the first section, and then the third row in the first section. This continues through each and every section. The indexPath.row variable is used to pull the first, second, or third country from the array.

Then add the following line of code:

cell.textLabel.text = country

This line of code takes the country variable and sets it to the UITableViewCell’s text. The country’s name will now be displayed on the newly created cell.

Add the final line of code:

return cell

The application is almost complete; however, one more change must be made inside of the storyboard. Open Main.storyboard and open the Inspector.

Next, click the Table View Controller Scene then double-click the yellow circle at the top. A blue box will surround the yellow circle when it is selected. Then open the Identity Inspector. The Identity Inspector is the third icon from the left. It looks like a small newspaper. Inside the class input box, type CountriesTableViewController (Figure 5-26).

CountriesTableViewController custom class

Figure 5-26. CountriesTableViewController custom class

This will attach the CountriesTableViewController.swift file to the Table View Controller Scene. The application is now complete; save your work and click the Play button in the upper left.

The app will launch and display the name, birthday, nationality, and photo as designed (Figure 5-27). Tap on the Show Countries button, and the navigation controller will push a new view controller onto the stack.

Running Passport app

Figure 5-27. Running Passport app

Notice each cell shows a different country (Figure 5-28). The back button in the upper-left corner is automatically created. The back button will use the title from the previous view controller, or it will state “Back” if there is no title provided. Click the Back button.

Countries visited

Figure 5-28. Countries visited

PERSONAL CHALLENGE

What changes need to be made to have this app show five countries? What about 10 countries? What about 1,000 countries?

Don’t worry if you received an error, warning, or your app did not run as expected. The best way to learn is to make mistakes. Practice makes perfect. A sample version of the project is available on AppSchool.com/book. Download it, compare, and try, try again. Don’t be scared to start the exercise over and walk through it until you get it right.