Hour 14. Navigating Information Using Table Views and Split View Controllers


What You’ll Learn in This Hour:

Image The types of table views

Image How to implement a simple table view and controller

Image Ways to add more structure and impact to a table with sections and cell images

Image The purpose of split view controllers

Image How to use a Master-Detail Application template


So far, our exploration of iOS development has included typical interface elements that focus on getting information from the user, with little attention paid to output. What’s missing is the ability to present categorized information in a structured manner. Everywhere you look (websites, books, applications on your computer), you see methods for displaying information in an attractive and orderly manner. iOS has its own conventions for displaying this type of information.

First, the table view. This UI element is essentially a categorized list, like what you see when browsing your iOS contacts. Second, the SplitViewController object combines tables, a sliding panel (essentially a popover that slides in from the side), and a detail view into an experience very similar to using Apple’s Mail app on the iPad or 5.5”+ iPhone. In this hour, we explore how to implement a table view, and then we take that knowledge and apply it to a universal application that navigates information using a table/navigation controller combination on smaller-screened iPhones and a split view on the iPad and “plus-sized” iPhones.

Understanding Tables

Like the other views you’ve seen in this book, a table view (UITable) holds information. A table view’s appearance, however, is slightly counterintuitive. Instead of showing up as a true table (like an Excel worksheet), a table view displays a single list of cells onscreen. Each cell can be structured to contain multiple pieces of information but is still a single unit. In addition, the cells can be broken into sections so that the clusters of information can be communicated visually. You might, for example, list computer models by manufacturers, or models of the Macintosh by year.

Table views respond to touch events and enable the user to easily scroll up and down through long lists of information and select individual cells through the help of a table view controller (UITableViewController) and two protocols: UITableViewDataSource and UITableViewDelegate.


Note

A table view controller is just a standard view controller that displays only a table view. You can use this if a table view is going to be your entire view. That said, it offers no benefits over using a standard view controller and adding a table view except that its delegate and data source variable properties are set automatically. That’s it!

In fact, by using a standard view controller and adding the two protocols, we can create tables that are sized any way we want within our views. We just need to connect the table’s delegate and data source outlets to the view controller class.

Because creating our own table view controller from a standard view controller offers the greatest flexibility, that’s what we do in this lesson.


Table Appearance

There are two basic styles of table views: plain and grouped, as demonstrated in Figure 14.1. Plain tables lack the clear visual separation of sections of the grouped tables, but are often implemented with a touchable index (like the Contacts list of people). Because of this, they are sometimes called indexed tables. This text continues to refer to them by the names (plain/grouped) designated in Xcode.

Image

FIGURE 14.1 Plain tables look like simple lists, whereas grouped tables have more distinct divisions.

Table Cells

Tables are only a container. To display content in a table, you must provide information to the table by configuring table view cells (UITableViewCell). By default, a cell can display a title, a detail label, an image, and an accessory—usually a disclosure indicator that tells the user he can “drill down” to more information through a push segue and a navigation controller. Figure 14.2 shows a sample table cell layout with each of these features active.

Image

FIGURE 14.2 Table cells make up the content of a table.

In addition to its visual design, each table cell has a unique identifier called the reuse identifier. This is used to refer to the cell layout by name when we’re coding and must be set when configuring the table.

Adding a Table View

To add a table to your view, drag the UITableView from the Object Library into one of your views. After adding the table, you can resize it to fill the view, or only a portion, if you prefer. If you drag a UITableViewController into the editor instead, you’ll get a whole new scene in the storyboard with a table filling the entire view.

Setting Table Attributes

After adding the view, you can set its styling by selecting the table view and then opening the Attributes Inspector (Option-Command-4) in the Interface Builder (IB) editor, demonstrated in Figure 14.3.

Image

FIGURE 14.3 Set the attributes of the table.

The first thing you’ll see is that the default table is set to use Dynamic Prototypes. This means that we can visually design the table and cell layout directly in IB—and is exactly what we want. The other option, Static Cells, is an older approach that does not offer the flexibility we now have in the latest versions of Xcode. You want to increase the Prototype Cells value to at least 1 (as is visible in Figure 14.3). This setting shows what the cells in the table will look like. You can update Prototype Cells to as many as you need; each one can take on a unique appearance and be accessed in code via its reuse identifier. (Just a minute and you’ll see where that’s set.)

Use the Style pop-up menu to choose between Plain and Grouped table styles, the Separator to choose the separator appearance between sections, and the Color pop-up to set the color of the lines that separate cells.

The Selection and Editing settings change the behavior of the table when it is touched. We won’t be getting into table editing, so the default settings are sufficient (allowing only one row at a time to be touched and highlighting it when it is touched).

Once the setup of the table container is complete, you need to design the cell prototype.

Setting the Prototype Cell Attributes

To control the cells that are displayed in a table, you must configure the prototype cell (or cells) that you want to use in your application. By default, there are no prototype cells made available when adding a table, so make sure you add one; otherwise, you won’t have anything to edit.

Begin editing the prototype by expanding the table view item within your view and selecting the table view cell (or just click the cell directly in the editor). Once the cell is highlighted, you can use its selection handle to increase the cell height. Everything else, however, needs the Attributes Inspector, as shown in Figure 14.4.

Image

FIGURE 14.4 Configure your prototype cell.


Caution: Sizing Consistency Is Crucial!

Increasing the cell height by dragging the selection handle changes the height listed in the Size Inspector for both the cell and its parent table view object. If you select the cell and use the Size Inspector alone to change the height, it will appear to work, but the table view object won’t inherit the changes. You must then select the table view separately, open the Size Inspector, and use the Row Height field to set the height to match the cell.

A different (and more consistent) approach is to forego using the selection handles to resize the cell. Instead, simply select the table view and use the Size Inspector to set the Row Height. The height you set on the table will be automatically inherited by the cell.


At the top of the Attributes Inspector is a cell style. Custom cells require you to create a custom subclass for UITableViewCell, which is beyond the scope of this book. Thankfully, most tables use one of the standard styles:

Image Basic: Only a cell title is visible.

Image Right Detail: Title and detail labels are shown, detail on the right.

Image Left Detail: Title and detail labels are shown, detail on the left.

Image Subtitle: A detail label is displayed directly below the main cell title.


Tip

After setting a cell style, you can select the title and detail labels by clicking them within the prototype cell or the cell’s view hierarchy in the document outline. Once selected, you can use the Attributes Inspector to customize the appearance of the label as desired.


Use the Image drop-down to add an image to the cell. You will, of course, need to have image resources in your project for anything to show up. Bear in mind that the image you set and the title/detail text you stylize in the prototype cell are just placeholders. They’ll be replaced by actual data in code.

The selection and accessory drop-downs configure the color of the cell when it is selected and the accessory graphic (usually a disclosure indicator) that can be added to the right side of a cell. The other attributes, with the exception of the identifier, are used to configure editable table cells.

The identifier is probably the most important attribute to set. Without it, you cannot reference a prototype cell and display content in your code. You can use any arbitrary string for the identifier. Apple uses Cell in its sample code, for example. If you add multiple prototype cells with different designs, each must have its own unique identifier.

That covers the visual design of a table. Now, how about the code that makes it work? To populate a table, you use the Connections Inspector for the table view to connect the delegate and data source outlets to the class (probably your view controller) that will implement the UITableViewDelegate and UITableViewDataSource protocols, respectively.

The Table View Data Source Protocol

The table view data source protocol (UITableViewDataSource) includes methods that describe how much information the table will be displaying, as well as provide the UITableViewCell objects to the application for display. This is a bit of a contrast to what we experienced with picker views, where the data source provided only the amount of information being displayed.

We focus on four of the most useful data source methods:

Image numberOfSectionsInTableView: Returns the number of sections that the table will be divided into.

Image tableView:numberOfRowsInSection: Returns the number of rows of information within a given section. Sections are numbered starting at 0.

Image tableView:titleForHeaderInSection: Returns a string that represents the section number provided to the method.

Image tableView:cellForRowAtIndexPath: Returns a properly configured cell object to display in the table.

For example, suppose that you want to create a table with two sections with the headings One and Two. The first section will have a single row, and the second will have two. This setup is handled by the first three methods, as demonstrated in Listing 14.1.

LISTING 14.1 Configuring the Sections and Row Count for the Table View


 1:  func numberOfSectionsInTableView(tableView: UITableView) -> Int {
 2:      return 2
 3:  }
 4:
 5:  func tableView(tableView: UITableView,
 6:      numberOfRowsInSection section: Int) -> Int {
 7:      if section==0 {
 8:          return 1
 9:      } else {
10:          return 2
11:      }
12:  }
13:
14:  func tableView(tableView: UITableView,
15:      titleForHeaderInSection section: Int) -> String? {
16:      if section==0 {
17:          return "One"
18:      } else {
19:          return "Two"
20:      }
21:  }


Lines 1–3 implement the numberOfSectionsInTableView method, which returns 2; the table will have two sections.

Lines 5–12 handle the tableView:numberOfRowsInSection method. When the section number specified by iOS is 0 (the first section), the method returns 1 (line 10). When the section is 1 (the second table section), the method returns 2 (line 12).

Lines 14–21 implement the tableView:titleForHeaderInSection method, which is very similar to the previous method, except it returns a string to be used as a section’s title. If the section is 0, the method returns "One" (line 20); otherwise, it returns "Two" (line 22).

These three methods set up the table’s layout, but to provide content to a table cell, you must implement the tableView:cellForRowAtIndexPath. iOS passes an object called an NSIndexPath to the method. This includes a variable property called section and one named row that identify the specific cell you should return. The method initializes a UITableViewCell and sets its textLabel, detailTextLabel, and imageView variable properties to change the information it displays.

Let’s walk through a quick implementation of a method that could provide cell objects to a table view. This assumes that the table cell has the identifier "Cell"; that it has been configured to show an image, title, and detail label; and that we have an image file named generic.png that we want displayed in every cell. It’s not a very realistic real-world example, but we’ll save that for the exercises. Listing 14.2 shows a possible implementation of tableView:cellForRowAtIndexPath.

LISTING 14.2 A Silly Implementation of tableView:cellForRowAtIndexPath


 1: func tableView(tableView: UITableView,
 2:     cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
 3:
 4:     let cell: UITableViewCell =
 5:         tableView.dequeueReusableCellWithIdentifier("Cell") as UITableViewCell
 6:     let cellImage: UIImage = UIImage(named: "generic.png")!
 7:
 8:     if indexPath.section==0 {
 9:         cell.textLabel!.text="Section 0, Row 0"
10:         cell.detailTextLabel!.text="Detail goes here."
11:         cell.imageView!.image=cellImage
12:     } else {
13:         if indexPath.row==0 {
14:             cell.textLabel!.text="Section 1, Row 0"
15:             cell.detailTextLabel!.text="Detail goes here."
16:             cell.imageView!.image=cellImage
17:         } else {
18:             cell.textLabel!.text="Section 1, Row 1"
19:             cell.detailTextLabel!.text="Detail goes here."
20:             cell.imageView!.image=cellImage
21:         }
22:     }
23:
24:     return cell
25: }


The method starts on line 4 by initializing a cell object based on the prototype call with the identifier "Cell". All implementations of this method should start with these lines.

Lines 6 initializes a UIImage (cellImage) from a project resource named generic.png. In a real project, you’ll likely want a different image for each cell.

Lines 8–12 configure the cell for the first section (indexPath.section==0). Because there is only a single row, we don’t need to worry about which row is being requested.

The cell is populated with data by setting its textLabel, detailTextLabel, and imageView variable properties. These are just instances of UILabel and a UIImageView, so in the case of the labels, we need to assign the text variable property, and for the image view, we set the image variable property.

Lines 13–21 set up the cells for the second section (1). In the second section, however, the row matters (since it has two rows). So the row is checked to see whether it is 0 or 1 (the first two rows), and then the contents of the cell are modified appropriately.

Line 24 returns the properly initialized cell.

That’s all it takes to get a table view populated, but to react to a user’s touch inside a table row, we need a method from the UITableViewDelegate protocol.

The Table View Delegate Protocol

The table view delegate protocol includes several methods for reacting to a user’s interactions with a table—everything from selection rows to touching the disclosure indicator to editing the cells in the rows. For our purposes, we’re only interested in when the user touches and highlights a row, so we will use the tableView:didSelectRowAtIndexPath method.

A table selection is provided to the tableView:didSelectRowAtIndexPath method using the NSIndexPath object. This means that you need to react to the section and row that the user touched. Listing 14.3 shows how we can react to a touch in the hypothetical table we’ve created.

LISTING 14.3 Reacting to a User’s Touch


func tableView(tableView: UITableView,
    didSelectRowAtIndexPath indexPath: NSIndexPath) {

    if indexPath.section==0 {
        // The user chose the first cell in the first section
    } else {
        if indexPath.row==0 {
           // The user chose the first row in the second section
        } else {
           // The user chose the second row in the second section
        }
    }

}


Nothing to it, right? The comparison logic is the same as Listing 14.2, so refer back to it if you’re wondering how the method determines which section/row is which.

That’s all we need for tables! We’ll now take a look at a special type of view controller and application template that pulls together navigation controllers, tables, and a universal iPad/iPhone application. You won’t need any new coding knowledge for this, but you will need to call on several things you’ve learned over the past few hours.


Tip

If you want to make full use of tables in your applications, you should read Apple’s documentation on UITableView and the UITableViewDataSource and UITableViewDelegate protocols. There’s a great deal of information available that can help you create tables with far more features than we can fit in a single hour’s lesson.


Exploring the Split View Controller

The second interface element we look at in this lesson is the split view controller. This controller isn’t just a feature that you add to an application; it is a structure that you build entire applications around.

A split view controller enables you to display two distinct view controller scenes in a single iPad or 5.5”+ iPhone. In landscape mode, the left third of the screen is occupied by the “master” view controller’s scene, and the right half contains the “detail” view controller scene. In portrait mode, the scene managed by the detail view controller shifts to take up the entire screen. You can use any views and controls you want in either of these areas—tab bar controllers, navigation controllers, and so on. On smaller iPhone screens, a “master” table view slides away to reveal a “detail” screen.

As mentioned earlier, the split view controller pulls together tables, a sliding panel, and a number of views. For users, it works like this: In landscape mode on iPads and large-screened iPhones, a table is displayed on the left so that the user can make a selection. When an element from the table is touched, the detail area shows (guess what?) details about the selection. If the device is rotated to a portrait mode, the table disappears, and the detail fills the screen. To navigate between different items in portrait mode, the user can touch a toolbar button or swipe in from the left side of the screen to display the detail table. Small-screen devices show the same table, wait for a user selection, and then reveal a new screen with detail information. A different user interface, but using the same classes and storyboard.

This application structure is widely used in both Apple and third-party applications. Mail, for example, uses a split interface for showing the list of messages and the body of a selected message. Popular note/file management apps, like Evernote, show a list of entries on the left and the content in the detail view, as shown in Figure 14.5.

Image

FIGURE 14.5 A table on the left and details on the right.

Implementing the Split View Controller

You can add the split view controller to your project by dragging it from the Object Library to your storyboard. It must be set as the initial scene in the storyboard; you cannot transition to it from any other view. When first added, as shown in Figure 14.6, it automatically includes several default views that are associated with the master and detail view controllers.

Image

FIGURE 14.6 Adding a split view controller includes a bit of cruft in your application interface.

These can be removed, new scenes added, and then the relationships between the split view controller and the master/detail scenes reestablished by Control-dragging from the split view controller object to the master scene and the detail scene and choosing Relationship, Master View Controller and Relationship, Detail View Controller, respectively, when prompted.


Tip

The split view controller appears in the IB editor in portrait mode by default. This gives the appearance of only having a single scene (the detail scene) contained in it. To switch it to the landscape mode and see the division between the master/detail portions of the display, first select the split view controller object. Next open the Attributes Inspector (Option-Command-4) and change the Size popup menu to iPad Full Screen, and Orientation pop-up menu to Landscape.

This is purely a visual change in the editor; it doesn’t change how the application functions at all.


After you’ve established a split view controller in your application, you build it out as you normally would, but you essentially have two unique “parts” that can work independently of one another (the master scene and the detail scene). To share information between them, each side’s view controller can access the other side through the split view controller that manages them.

The master view controller, for example, can return the detail view controller using this:

splitViewController?.viewControllers.last

And the detail view controller can get ahold of the master view controller with the following:

splitViewController?.viewControllers.first

The splitViewController variable property contains an array called viewControllers. Using the Array method last, we can grab the last item in the view controller array (the detail view). Using the method first, we can get the first item in the array (the master view). From there, we’re set; both view controllers can exchange information freely.

The Master-Detail Application Template

If it seems like I rushed through that, it’s because I did. The split view controller can be whatever you want it to be; however, to be used as Apple intended, it needs a navigation controller and quite a few different views. Doing all the setup necessary to make it work like the iPad Mail app, for example, would take quite a few hours out of this book.

Thankfully, Apple has created an application template called the Master-Detail Application template that makes this easy. In fact, Apple recommends, in its documentation of the split view controller, that you use this template as a starting point instead of starting from scratch.

All the functionality is handled for you automatically. No need to deal with the sliding panel, no need to set up view controllers, no handling the swipe gesture, no manually rearranging the view after a user rotates the device. It just works. Your only job is to supply the content for the table, implemented in the template’s MasterViewController class (a table) and the detail view, handled by the DetailViewController class.

It gets better! The Master-Detail Application template is set up so that you can easily create a universal application that works on any device. The same code works for both iPad and iPhone, so you’ll get another taste of universal development in this exercise. Before we get to that, however, first we need to try our hand at creating a table.

A Simple Table View Application

To begin this hour’s tutorials, we create a two-section table that lists the names of common red and blue flowers under a heading that describes their colors. Each table cell, in addition to a title, contains an image of the flower and a disclosure indicator. When the user touches an entry in the table, an alert appears showing the chosen flower name and color. The final application will look similar to Figure 14.7.

Image

FIGURE 14.7 In our first application, we create a table that can react to a user’s interactions.

Implementation Overview

The skills needed to add table views to your projects are very similar to what you learned when working with pickers. We add a table view to a Single View Application template and then implement the UITableView delegate and data source protocols.

We use two arrays to store the data for the table, but no additional outlets or actions are needed; everything is handled by conforming to the appropriate protocols.

Setting Up the Project

Begin by creating a new iOS single-view project named FlowerColorTable. We use the standard ViewController class to act as our table’s view controller because this gives us the greatest flexibility in the implementation.

Adding the Image Resources

The table we create will display images for each flower it lists. To add the flower images, first open the project navigator. Next, click the Images.xcassets icon to open the project’s image assets. Now, drag the Images folder from this hour’s Projects folder in the Finder into the left-hand column inside the asset catalog.

Planning the Variables and Connections

In this project, we need two arrays (we’ll call them redFlowers and blueFlowers) that, as their names imply, contain a list of the red flowers and blue flowers that we are going to display. These arrays will be added as constants. The image file for each flower is titled after the flower’s name; we’ll take advantage of this to simplify the project.

The only connections required are from the data source and delegate outlets of the UITableView object back to ViewController.

Adding Table Section Constants

Instead of referring to table sections by number, it is helpful to work with them a bit more abstractly. To this end, we can add a few constants to ViewController.swift so that, instead of needing to remember that “section 0” is the red flower, we can refer to it by something a bit friendlier.

Edit ViewController.swift and add these lines so that they follow the class line:

let kSectionCount:Int = 2
let kRedSection: Int = 0
let kBlueSection: Int = 1

The first constant, kSectionCount, is the number of sections the table will display; the other two constants, kRedSection and kBlueSection, are used to refer to the individual sections within the table view.

That’s all the setup that’s required.

Designing the Interface

Open the Main.storyboard file and use the Attributes Inspector to change the simulated size to something familiar—like a 4.7-inch iPhone.

Next, drag an instance of a table view (UITableView) into your application’s scene. Resize the table view to fit the entire scene.


Caution: Table View, Not Table View Controller

Remember, drag just a table view into the editor, not a table view controller. If you drag in a controller, it will add an entirely new scene. If you want to do that, no problem, but you’ll need to delete the existing scene and set the table’s view controller identity to the ViewController class.


Now, select the Table View and open the Attributes Inspector (Option-Command-4). Use the Content pop-up menu to choose Dynamic Prototypes (if it isn’t already selected), and then update the Prototype Cells count to 1. Set the table style to Grouped if you want to exactly match my example, as shown in Figure 14.8.

Image

FIGURE 14.8 Set the table view attributes.

Congratulations, you’ve just completed 99% of the interface design! All that remains is configuring the prototype cell. Select the cell by clicking it in the editor or by expanding the table view object in the document outline and selecting the table cell object. Within the Attributes Inspector, begin by setting the cell identifier to flowerCell; if you leave this step out, your application won’t work!

Next, set the style to Basic, and use the Image pop-up menu to pick one of the image resources you added earlier. Use the Accessory pop-up to add the Disclosure Indicator accessory to the cell and finish its layout.

At this point, your cell will be ready, but it’s probably looking a bit cramped. Click and drag the handle on the bottom of the cell to expand its height until it looks the way you want. Figure 14.9 shows my completed user interface (UI).

Image

FIGURE 14.9 A completed prototype cell.

Connecting the Delegate and Data Source Outlets

Isn’t it nice to work on a project that doesn’t have a bazillion outlets and actions to define? I thought so. For our table to show information and react to a user’s touch, it needs to know where it can find its delegate and data source prototype methods. We implement those in the ViewController class, so let’s get it connected.

Select the table view object in the scene, and then open the Connections Inspector (Option-Command-6). In the Connections Inspector, drag from the delegate outlet to the ViewController object in the document outline. Do the same for the dataSource outlet. The Connections Inspector should now resemble Figure 14.10.

Image

FIGURE 14.10 Connect the data source and delegate to the view controller.

The UI is done and the connections are in place. All we need now is code.

Implementing the Application Logic

At the start of this hour, you learned about the two protocols that we must implement to populate a table view (UITableViewDataSource) and react to a user’s selection of a cell (UITableViewDelegate). The difference between the earlier example and now is that we will be pulling data from an array. Because the array doesn’t yet exist, creating it sounds like a good first implementation task, don’t you think?

Populating the Flower Arrays

We need two arrays to populate the table: one containing red flowers, and one containing blue. We need to access these throughout our class, so declaring them as variable properties or constants is required. Edit ViewController.swift and add two constants for redFlowers and blueFlowers, after the constants you created earlier:

let redFlowers: [String] = ["Gerbera","Peony","Rose","Poppy"]
let blueFlowers: [String] = ["Hyacinth","Hydrangea","Sea Holly","Phlox","Iris"]

These two lines both declare and define the array constants with the names of flowers.

We now have all the data we need for the table view data source protocol—constants to determine the layout of the table and flower array constants to provide information.

Implementing the Table View Data Source Protocol

We need to implement a total of four data source methods to provide information to the table in our view: numberOfSectionsInTableView, tableView:numberOfRowsInSection, tableView:titleForHeaderInSection, and tableView:cellForRowAtIndexPath. We’ll work through each of these, one at a time, but first we need to declare our intention to conform the ViewController class to the UITableViewDataSource protocol. Update the class line in ViewController.swift to read as follows:

class ViewController: UIViewController, UITableViewDataSource {

Now, we add the methods, starting with numberOfSectionsInTableView. This method returns the number of sections the table will display, something we’ve conveniently stored in kSectionCount. Return this constant, and we’re done. Implement this method as shown in Listing 14.4.

LISTING 14.4 Returning the Number of Sections in the Table


func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return kSectionCount
}


The next method, tableView:numberOfRowsInSection, returns the number of rows within a section (that is, the number of red flowers in the red section and the number of blue flowers in the blue section). By checking the provided section parameter against the red and blue section constants and then using the array method count to return a count of the items in the array, the implementation becomes trivial, as demonstrated in Listing 14.5.

LISTING 14.5 Returning a Count of the Rows (Array Elements) in Each Section


func tableView(tableView: UITableView,
    numberOfRowsInSection section: Int) -> Int {
    switch section {
        case kRedSection:
            return redFlowers.count
        case kBlueSection:
            return blueFlowers.count
        default:
            return 0
    }
}


The only thing that might throw you off here is the switch statement. This looks at the incoming section parameter, and if it matches the kRedSection constant, it returns a count of the redFlowers array and does the same for the blue flowers. The default case should be impossible to reach, so returning 0 is fine.

The tableView:titleForHeaderInSection method is even easier. It, again, must check the section against the red and blue flower constants, but it only needs to return a string ("Red" or "Blue") to title each section. Add the implementation in Listing 14.6 to your project.

LISTING 14.6 Returning a Heading for Each Section


func tableView(tableView: UITableView,
    titleForHeaderInSection section: Int) -> String? {
    switch section {
        case kRedSection:
            return "Red"
        case kBlueSection:
            return "Blue"
        default:
            return "Unknown"
    }
}


The last data source method is the most complicated and the most important. It provides the cell objects to the table view for display. In this method, we must create a new cell from the flowerCell identifier we configured in IB and then populate its imageView and textLabel attributes with the data we want displayed for each incoming indexPath parameter. Create this method in ViewController.swift using Listing 14.7.

LISTING 14.7 Configuring a Cell to Display in the Table View


 1: func tableView(tableView: UITableView,
 2:     cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
 3:
 4:     let cell: UITableViewCell =
 5:     tableView.dequeueReusableCellWithIdentifier("flowerCell") as UITableViewCell
 6:
 7:     switch (indexPath.section) {
 8:         case kRedSection:
 9:             cell.textLabel!.text=redFlowers[indexPath.row]
10:         case kBlueSection:
11:             cell.textLabel!.text=blueFlowers[indexPath.row]
12:         default:
13:             cell.textLabel!.text="Unknown"
14:     }
15:
16:     let flowerImage: UIImage=UIImage(named: cell.textLabel!.text!)!
17:     cell.imageView!.image=flowerImage
18:     return cell
19: }


Lines 4–5 create a new UITableViewCell object named cell based on the prototype cell we created with the identifier flowerCell.

Lines 7–17 handle the majority of the logic. By looking at the section variable of the incoming indexPath parameter, we can determine whether iOS is looking for something from the red or blue flower arrays. The indexPath row variable, however, identifies the row number within the section whose cell we need to configure.

Line 9 uses the row to index into the red flower array, setting the textLabel of the cell to the string stored at that position in the array. Line 11 does the same for the blue array.

Lines 12–13 should never be reached.

To get and set the image of the flower, we can implement the logic in one place, rather than separately for red and blue flowers. How? Using the textLabel variable property that we’ve already configured for the cell, remember—the name of the flower is also the name of an image file!

Line 16 declares a new UIImage object, flowerImage, by using the existing cell object textLabel.text string to find an image with the same name.


Tip

iOS will automatically find the right PNG image from just the base name of the file; no extension is required. This is why we can take the shortcut of using the name of the flower (which is identical to the base image name) to retrieve the image file.


Line 17 sets the cell imageView object’s image variable property to the newly created flowerImage.

Finally, the fully configured cell is returned in line 18.

You should now be able to build the application and view a beautiful scrolling list of flowers, but touching a row won’t do a thing. To react to a row’s touch, we must implement one more method, this time in the UITableViewDelegate protocol.

Implementing the Table View Delegate Protocol

The table view delegate protocol handles a user’s interactions with a table. To detect that a user has selected a cell in the table, we must implement the delegate method tableView:didSelectRowAtIndexPath. This method will automatically be called when the user makes a selection, and the incoming IndexPath parameter’s section and row variable properties tell us exactly what the user touched.

Before writing the method, update the class line in ViewController.swift one more time to show that we will be also conforming to the UITableViewDelegate protocol:

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

How you react to a row selection event is up to you, but for the sake of this example, we’re going to use UIAlertView to display a message. The implementation, shown in Listing 14.8, should look very familiar by this point. Add this delegate method to the ViewController.swift file.

LISTING 14.8 Handling a Row Selection Event


 1: func tableView(tableView: UITableView,
 2:     didSelectRowAtIndexPath indexPath: NSIndexPath) {
 3:
 4:     var flowerMessage: String
 5:
 6:     switch indexPath.section {
 7:         case kRedSection:
 8:             flowerMessage =
 9:                 "You chose the red flower - (redFlowers[indexPath.row])"
10:         case kBlueSection:
11:             flowerMessage =
12:                 "You chose the blue flower - (blueFlowers[indexPath.row])"
13:         default:
14:             flowerMessage = "I have no idea what you chose?!"
15:     }
16:
17:     let alertController = UIAlertController(title: "Flower Selected",
18:         message: flowerMessage,
19:         preferredStyle: UIAlertControllerStyle.Alert)
20:
21:     let defaultAction = UIAlertAction(title: "Ok",
22:         style: UIAlertActionStyle.Cancel,
23:         handler: nil)
24:
25:     alertController.addAction(defaultAction)
26:     presentViewController(alertController, animated: true, completion: nil)
27: }


Lines 4 declares flowerMessage that will be used for the message string shown to the user.

Lines 6–15 use a switch statement with indexPath.section to determine which flower array our selection comes from and the indexPath.row value to identify the specific element of the array that was chosen. The flowerMessage string is formatted to contain the value of the selection.

Lines 17–26 create and display an alert controller instance (alertController) containing the message string (flowerMessage).

Building the Application

After adding the delegate method to the implementation, build and run the application. You will now be able to scroll up and down through a list of flowers divided into section. Each cell in the table is configured with an image and a title string and shows a disclosure indicator (showing that touching it will do something).

Selecting a row displays an alert that identifies both the section where the touch occurred and the individual item the user selected. Of course, a “real” application would do something a bit more, so we will end this hour by doing something a bit more spectacular: creating a universal app that uses split view controllers, navigation controllers, tables, and web views.

Sounds difficult, but you’ll find that the only real code required is what you just wrote while building a table.

Creating a Master-Detail Application

With a basic understanding of table controllers under our belt, we can move on to building an application that takes things to the next level by implementing a split view controller. Not only that, it will run on both the iPad and iPhone. Apple’s Master-Detail Application template takes care of the tough stuff; we just need to provide content and handle a few loose ends that Apple left lying around.

This tutorial uses what we know about tables to create a list of flowers, by color, including images and a detail URL for each row. It also enables the user to touch a specific flower and show a detail view. The detail view loads the content of a Wikipedia article for the selected flower. The finished application will resemble Figure 14.11.

Image

FIGURE 14.11 Our master-detail application will show flowers, including thumbnails, and details on specific flower types.

Implementation Overview

In the preceding example, we created a table view and went through all the steps of adding methods to display content. We need to repeat that process again, but UI is already provided. We will, however, need to make a few changes to the storyboard before things will work as expected.

To manage the data, we use a combination of a dictionary and an array. In Hour 15, “Reading and Writing Application Data,” you’ll learn about persistent data storage, which will simplify the use of data even more in your future projects.

A big change in this project is that we will be building it as a universal application, for both iPhone and iPad. You learn more about universal applications in Hour 23, “Building Universal Applications.” For now, however, you need to know just one thing: The project will contain one storyboard that services both platforms. It does this through the iOS 8 miracle of size classes (yes, also in Hour 23).

Setting Up the Project

Start Xcode and create a new project using the Master-Detail Application template. Name the project FlowerDetail. Be sure, when stepping through the project creation assistant, that you choose Universal from the device family selection and do not use Core Data.

The Master-Detail Application template does all the hard work of setting up scenes and view controllers for displaying a table (MasterViewController) and one for showing details (DetailViewController). This is the “heart and soul” of many applications and gives us a great starting point for adding functionality.

Adding the Image Resources

As in the previous tutorial, we want to display images of flowers within a table. Add the flower images by dragging the Images folder into your Images.xcassets image catalog—exactly the same as the last exercise.

Understanding the Split View Controller Hierarchy

After creating the new project, explore the Main.storyboard file. You’ll notice an interesting hierarchy, as shown in Figure 14.12.

Image

FIGURE 14.12 The storyboard contains a split view controller, which connects to additional view controllers.

Connected to the split view controller are two navigation controllers (UINavigationController). From the master navigation controller is another connection to a scene with a table view (UITableView); this is the master scene and corresponds to the MasterViewController class files.

From the detail navigation controller is a connection to a simple empty scene; this is the detail scene and is handled by the DetailViewController class. The navigation bar at the top of the detail scene includes a button for accessing the master view and making another selection.

We need to make several changes to these files, but we’ll get back to that in a few minutes. Let’s take a look at the variables and connections we need to power this monster.

Planning the Variables and Connections

Apple already has a number of variables and connections established in the project. You’re welcome to review them and trace their function or just assume that Apple knows how to build software and focus on adding additional functionality. I choose the latter.

For our part, we will add two Array variable properties to the MasterViewController: flowerData and flowerSections. The first holds objects (dictionaries) that describe each flower, and the second holds the names (strings) of the sections within the table that we are creating. This structure makes it easy to interact with the table view data source and delegate methods.

In the DetailViewController, we add one outlet (detailWebView) for a UIWebView object that will be added to the interface and display details about a selected flower. That’s the only additional object that we need to “talk” to.

Tweaking the Interface

In this tutorial, we aren’t determining the application interface so much as the template is providing it for us. That said, we still need to make some changes to the storyboard. Select it in the project navigator to begin making edits.

Updating the Master Scene

To update the master scene, follow these steps:

1. Scroll to the upper-right corner of the storyboard. There you should see the master scene’s table view, currently titled Master in the navigation bar.

2. Double-click the title and change it to read Flower Type.

3. Select the table view in the master scene object hierarchy (it’s best to use the document outline for this) and open the Attributes Inspector (Option-Command-4).

4. Use the inspector to change the table style to Grouped.

5. Turning your attention to the table cell itself, set the cell identifier to flowerCell and the style to Subtitle. This style includes a title label and a detail label displayed underneath the title (a subtitle); we use that to show a Wikipedia URL unique to each flower.

6. Choose one of the images from the resources you added to the project so that it is previewed in the prototype cell.

7. Set a disclosure indicator using the Accessory pop-up menu, if you want.

8. Finish the design by selecting the subtitle label and changing its font size to 10 (or smaller). Then select the cell and use its handle to expand the cell to a vertical size that is visually appealing. Figure 14.13 shows my finished view.

Image

FIGURE 14.13 Tweak the master scene as shown here.

9. Before moving to the Detail scene, scroll to the navigation controller for the table view you just edited. Click in the middle of the scene to select its view controller.

10. Using the Attributes Inspector (Option-Command-4), set the view controller title to Flower Type. This isn’t necessary for the application to run, but if you don’t do this, the application will show Master for the flower list in some orientations.

Updating the Detail Scene

To update the detail scene, follow these steps:

1. Scroll down from the master scene and you should find yourself staring at a big white scene with a label that reads Detail View Content Goes Here.

2. Update the label to say Please Choose a Flower, because it is the first thing the user will see on the large-screen version of the app.

3. Drag a web view (UIWebView) from the Object Library into the scene.

4. Size it to fill the entire view; this is used to display a Wikipedia page describing the flower the user selected.

5. Select the web view, then choose Editor, Pin, Leading Space to Superview. Repeat this for Trailing Space, Top Space, and Bottom Space.

6. Position the “choose a flower” label on top of the web view by selecting the web view and using the Editor, Arrange, Send to Back menu option or by dragging the web view to the top of the view hierarchy in the document outline.

7. Finish the detail scene by updating its navigation bar title. Double-click the title and change it to read Flower Detail.

In step 5, we created constraints—similar to what we did in the universal project in Hour 11, “Implementing Multiple Scenes and Popovers.” You’ll learn more about this in Hour 16, “Building Responsive User Interfaces,” but for now understand that these constraints will keep the web view sized to fill whatever sized screen it is on.

Creating and Connecting the Web View Outlet

Select the web view in the IB editor, and then switch to the assistant editor; it should display the DetailViewController.swifts file.

Control-drag from the web view to just below the existing variable declarations (there should be a detailDescriptionLabel variable property automatically added), and create a new outlet named detailWebView, as shown in Figure 14.14. You’ve just finished the interface and connections.

Image

FIGURE 14.14 Connect the new web view to an outlet in the detail view controller.

Implementing the Application Data Source

In the previous table implementation project, we used multiple arrays and switch statements to differentiate between the different sections of flowers. This time around, however, we need to track the flower sections, names, image resources, and the detail URL that will be displayed.

Creating the Application Data Structures

What the application needs to store is quite a bit of data for simple arrays. Instead, we make use of an Array of Dictionaries to hold the specific attributes of each flower and a separate Array to hold the names of each section. We’ll index into each based on the current section/row being displayed, so no more switch statements.

To begin, we will need to update MasterViewController.swift to include variable property declarations for flowerData and flowerSections. Add these lines following the class line in MasterViewController.swift:

var flowerData: [AnyObject] = []
var flowerSections: [String] = []

The two arrays, referenced through flowerData and flowerSections, will hold our flower and section information, respectively. We start by initializing the arrays as empty, and will populate them within the createFlowerData method.

Next step? Loading the data! Implement the createFlowerData method in MasterViewController.swift, as shown in Listing 14.9. It will likely look a bit strange, but we cover the how’s and why’s in a minute.

LISTING 14.9 Populating the Flower Data Structures


 1: func createFlowerData() {
 2:     var redFlowers: [Dictionary<String,String>] = []
 3:     var blueFlowers: [Dictionary<String,String>] = []
 4:
 5:     flowerSections = ["Red Flowers","Blue Flowers"]
 6:
 7:     redFlowers.append(["name":"Poppy","picture":"Poppy.png",
 8:         "url":"http://en.wikipedia.org/wiki/Poppy"])
...
19:     redFlowers.append(["name":"Straw Flower","picture":"Strawflower.png",
20:         "url":"http://en.wikipedia.org/wiki/Peony"])
21:
22:     blueFlowers.append(["name":"Hyacinth","picture":"Hyacinth.png",
23:         "url":"http://en.wikipedia.org/wiki/Hyacinth_(flower)"])
...
34:     blueFlowers.append(["name":"Iris","picture":"Iris.png",
35:         "url":"http://en.wikipedia.org/wiki/Iris_(plant)"])
36:
37:     flowerData=[redFlowers,blueFlowers]
38: }


First, let’s concentrate on the individual flower data within each section. Lines 2 and 3 define two Arrays: redFlowers and blueFlowers. Both are defined as being a Dictionary where the keys and values are Strings.

The flowerSections array is initialized in line 5. The section names are added to the array so that their indexes can be referenced by section number. For example, Red Flowers is added first, so it is accessed by index (and section number) 0. Blue Flowers is added second and will be accessed through index 1. When we want to get the label for a section, we just reference it as flowerSections[section].

The flowerData structure is a bit more complicated. As with the flowerSections array, we want to be able to access information by section. We also want to be able to store multiple flowers per section, and multiple pieces of data per flower.

So, how can we get this done? By first dealing with the flowers in each section by populating redFlowers and blueFlowers. Lines 7–35 do just that; the code initializes Dictionaries with key/value pairs for the flower’s name (name), image file (picture), and Wikipedia reference (url) and inserts it into each of the two arrays.

Wait a second. Doesn’t this leave us with two arrays when we wanted to consolidate all the data into one? Yes, but we’re not done. Lines 37 creates this final array flowerData using the two redFlowers and blueFlowers arrays. What this means for our application is that we can reference the red flower array as flowerData[0] and the blue flowers as flowerData[1] (corresponding, as we wanted, to the appropriate table sections).

The end result will be a structure in memory that resembles Table 14.1.

Image

TABLE 14.1 The flowerData Structure


Note

The data that we included in the listing of the createFlowerData method is a small subset of what is used in the actual project files. If you would like to use the full dataset in your code, you can copy it from this hour’s project files or add it manually to the method using these values.


Populating the Data Structures

The createFlowerData method is now ready for use. We can call it from within the MasterViewController’s viewDidLoad method. Update the method in MasterViewController.swift by adding the following line at the end of its implementation:

createFlowerData()


Caution: Look, But Don’t Touch

Unless you’re positive of its purpose, be sure not to disturb any of the existing code in the project template files. Changing Apple’s template code can render the project inoperable.


Implementing the Master View Controller

We’ve reached the point where we can build out our table view in the MasterViewController class. Very little changes between how we implemented our initial tutorial table controller and how we will be building this one. Once again, we need to satisfy the appropriate data source and delegate protocols to add an interface and event handling to our data.

The biggest change to the implementation is how we access our data. Because we’ve built a somewhat complex structure of arrays of dictionaries, we need to make absolutely sure that we’re referencing the data that we intend to be.

Creating the Table View Data Source Methods

Instead of completely rehashing the implementation details, let’s just review how we can return the needed information to the various methods.

As with the previous tutorial, start by implementing the three basic data source methods within MasterViewController.swift. Remember that these methods (numberOfSectionsInTableView, tableView:numberOfRowsInSection, and tableView:titleforHeaderInSection) must return the number of sections, the rows within each section, and the titles for the sections, respectively.

To return the number of sections, we just need to return the count of the elements in the flowerSections array:

return flowerSections.count

Retrieving the number of rows within a given section is only slightly more difficult. Because the flowerData array contains an array for each section, we must first access the appropriate array for the section, and then return its count:

return flowerData[section].count

Finally, provide the label for a given section in the tableView:titleforHeaderInSection method (you’ll need to add this method into MasterViewController.swift). The application should index into the flowerSections array by the section value and return the string at that location:

return flowerSections[section]

Update the appropriate methods in MasterViewController.swift so that they return these values. As you can see, each of these three method implementations is now a single line (hopefully making up for what seemed to be a complicated structure holding the data).

Creating the Table Cell

Now we’re left with the most important method of the data source protocol: tableView:cellForRowAtIndexPath. Unlike the previous tutorial, we need to dig down into our data structures to retrieve the correct results. Let’s review the different pieces of code required in the implementation.

First, we must declare and initialize a new cell object using the flowerCell identifier we established in the prototype cell:

let cell = tableView.dequeueReusableCellWithIdentifier("flowerCell",
           forIndexPath: indexPath) as UITableViewCell

Nothing new there, but that’s where the similarities to what we’ve seen before end. To set a cell’s title label, detail label (subtitle), and image (all of which are optional values), we need code like this:

cell.textLabel!.text="Title String"
cell.detailTextLabel!.text="Detail String"
cell.imageView!.image=UIImage(named:"MyPicture"])

Not too bad, right? We have all the information; we just need to retrieve it. Let’s quickly review the three-level hierarchy of our flowerData structure:

flowerData(Array) → Array → Dictionary

The first level, the top flowerData array, corresponds to the sections within the table. The second level, another array contained within the flowerData array, corresponds to the rows within the section, and, finally, the Dictionary provides the individual pieces of information about each row. Refer back to Table 14.1 if you’re still having trouble picturing how information is organized.

So, how do we get to the individual pieces of data that are three layers deep? By first using the indexPath.section value to return the right array, and then from that, using the indexPath.row value to return the right dictionary, and then finally, using a key to return the correct value from the dictionary.

For example, to get the value that corresponds to the "name" key for a given section and row and assign it to a cell’s main label, we can write the following:

cell.textLabel!.text = flowerData[indexPath.section][indexPath.row]["name"] as String!

Because the flowerData structure is an Array of AnyObject, we must force it to a String through type casting. We also have to explicitly unwrap the value because the dictionary we ultimately reference could potentially not have a value for a given key. We know it does, so that’s okay. Applying the same logic, we can assign a cell object’s detail label to the value stored in the "url" key for a given section and row with this:

cell.detailTextLabel!.text = flowerData[indexPath.section][indexPath.row]["url"] as String!

Likewise, we can return and assign the image with the following:

cell.imageView!.image =
    UIImage(named: flowerData[indexPath.section][indexPath.row]["picture"] as String!)

The only other step is to return the fully configured cell. Implement this code in your MasterViewController.swift file now. Remove any additional lines cell-generating method, as we aren’t really interested in Apple’s sample implementation.

Your master view should now be able to display a table, but we still need to be able to handle the selection of an item in the table and update the detail view accordingly. Before that, however, we should disable a feature we don’t want: editing.

Disabling Editing

Apple has been kind enough to include the beginnings of some advanced features in the Master-Detail Application template, including the editing of the table view. This, however, isn’t something that we want within our project, and is unfortunately outside the scope of what we can cram into this book. To disable editing within this project, edit the MasterViewController.swift method tableView:canEditRowAtIndexPath to return false, as shown in Listing 14.10.

LISTING 14.10 Disabling Editing of Table Cells


override func tableView(tableView: UITableView,
    canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
    // Return false if you do not want the specified item to be editable.
    return false
}


Now, comment out (or remove) the lines related to buttons and button bars in the MasterViewController.swift viewDidLoad method. This will keep the user interface from displaying buttons for editing the table items. The final version of viewDidLoad (including changes you made earlier) should resemble Listing 14.11.

LISTING 14.11 Disabling Editing of the UI


override func viewDidLoad() {
    super.viewDidLoad()

    createFlowerData()

    if let split = self.splitViewController {
        let controllers = split.viewControllers
        self.detailViewController =
         controllers[controllers.count-1].topViewController as? DetailViewController
    }
}


Our project will now ignore attempts to edit the flower table; it’s time to handle the actions we do want the user to perform.

Handling Navigation Events from a Segue

In the previous tutorial application, we handled a touch event with the tableView:didSelectRowAtIndexPath UITableViewDelegate protocol method and displayed an alert to the user. In this example, Apple’s template is already set up to begin a segue when a selection is made. The method prepareForSegue (refer to Hour 11 for more details) will be used to pick up the selected table row and pass information to the detailViewController.

We’ll be communicating with the detailViewController through one of its Apple-provided variable properties (of type AnyObject) called detailItem. Because detailItem can point to any object, we set it to the Dictionary of the chosen flower; this will give us access to the name, url, and other keys directly within the detail view controller.

Implement prepareForSegue in MasterViewController.swift, as shown in Listing 14.12. Note that you’re just adding a single line (bolded) and removing a few of Apple’s sample implementation code chunks:

LISTING 14.12 Setting the Detail View Controller’s detailItem


override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "showDetail" {
        if let indexPath = self.tableView.indexPathForSelectedRow() {
            let controller = (segue.destinationViewController as
                UINavigationController).topViewController as DetailViewController
           controller.detailItem = self.flowerData[indexPath.section][indexPath.row]
            controller.navigationItem.leftBarButtonItem =
                self.splitViewController?.displayModeButtonItem()
            controller.navigationItem.leftItemsSupplementBackButton = true
        }
    }
}


When a flower is selected, it is passed to the detailViewController’s detailItem variable property.

Well now, that seems too easy, doesn’t it? There’s probably lots of work to be done trapping the event in the detail view controller and updating the view, right? Nope. To implement the detail view controller, we need to update a single method: configureView.

Implementing the Detail View Controller

We’ve already updated the detail view controller interface with a web view, and we know how it should work. When the user picks one of our flowers, the UIWebView instance (detailWebView) should be instructed to load the web address stored within the detailItem variable property. The method where we can implement this logic is configureView. It is automatically invoked whenever the detail view should update itself. Both configureView and detailItem are already in place, so all that we need is a tiny bit of logic.

Displaying the Detail View

While we could use detailItem directly, Apple begins its implementation of the configureView method with:

if let detail: AnyObject = self.detailItem {

This sets the detail variable to self.detailItem if detailItem is defined. This is a nicety that protects against detailItem not being properly defined. We’ll leave it in the code, but it means that we’ll be working with the variable detail, not detailItem.

Because detail is a single Dictionary for one of our flowers, we need to use the "url" key to access the URL string and turn that into a NSURL object. This is accomplished quite simply:

let detailURL: NSURL = NSURL(string: detail["url"] as String!)!

First we declare the NSURL object detailURL, and then we initialize it using the URL stored in the dictionary.

You might remember from earlier lessons that loading a web page in a web view is accomplished with the loadRequest method. This method takes an NSURLRequest object as its input parameter. Because we only have an NSURL (detailURL), we also need to use the NSURLRequest class method requestWithURL to return the appropriate object type. One additional line of code takes care of all of this:

detailWebView.loadRequest(NSURLRequest(URL: detailURL))

Now, remember that navigation item that we changed to read Flower Detail in the detail scene? Wouldn’t it be nifty to set that to the name of the flower (detailItem[@"name"])? We can! Using the navigationItem.title, we can update the title in the navigation bar and set it to whatever we want. The code to set the title in the bar at the top of the detail view becomes the following:

navigationItem.title = detail["name"] as String!

Finally, the label that displays the Choose a Flower message should be hidden after an initial selection is made. The variable (already included in the template) for the label is detailDescriptionLabel. Setting its hidden variable property to true hides the label:

detailDescriptionLabel.hidden = true

Unfortunately, if we put all of this together in configureView, it’s not going to work, because configureView may be called before the web view is even instantiated by the storyboard. To get around an error, we can do exactly what Apple did with configureView and wrap the code that displays the web view into the if statement that begins with the following:

if let webview = detailWebView {

Pull all of this together into a single method by updating configureView in DetailViewController.swift to read as shown in Listing 14.13.

LISTING 14.13 Configuring the Detail View Using the detailItem


func configureView() {
    // Update the user interface for the detail item.
    if let detail: AnyObject = self.detailItem {
        let detailURL: NSURL = NSURL(string: detail["url"] as String!)!
        if let webview = detailWebView {
            webview.loadRequest(NSURLRequest(URL: detailURL))
            navigationItem.title = detail["name"] as String!
            detailDescriptionLabel.hidden = true
        }
    }
}


Building the Application

Run and test the application on the iOS Simulator using both iPhone and iPad hardware simulation. Try rotating the device on the iPad and the iPhone 6+; the interface should update appropriately (and somewhat dramatically). Smaller devices, however, use the same code to provide the same functionality, but with a very different interface.

I know this is a somewhat unusual tutorial, but it’s one that I consider important. The Master-Detail Application template is used very, very often and can jump-start your development of high-quality tablet and handheld applications.

Further Exploration

Although the most “dramatic” part of this hour was implementing the UISplitViewController, there is a wealth of additional features to be uncovered in the topic of tables. To continue your experience in working with tables, I suggest focusing on a few important enhancements.

The first is expanding what you can do with table cells. Review the variable property list for UITableViewCell. In addition to the TextLabel and ImageView variable properties, you can make numerous other customizations—including setting backgrounds, detail labels, and much, much more. In fact, if the default table cell options do not provide everything you need, Interface Builder supports visual customization of table cells by creating a completely custom cell prototype.

Once you have a handle on the presentation of the table views, you can increase their functionality by implementing a few additional methods in your table view controller. Read the reference for UITableViewController, UITableViewDataSource, and UITableViewDelegate. You can quickly enable editing functionality for your table by implementing a handful of additional methods. You’ll need to spend some time thinking of what editing controls you want to use and what the intended result will be, but the basic functionality of deleting, reordering, and inserting rows (along with the associated graphic controls you’re used to seeing in iOS applications) will come along “for free” as you implement the methods.

Summary

This hour introduced two of the most important iOS interface components: the table view and the split view controller. Table views enable users to sort through large amounts of information in an orderly manner. We covered how table cells are populated, including text and images, as well as the mechanism by which cell selection occurs.

We also explored the role of the split view controller in managing a master view and detail view and how it can be easily implemented by way of the Master-Detail Application template.

Coming away from this hour, you should feel comfortable working with tables in your applications and building basic apps using the new project template.

Q&A

Q. What is the most efficient way to provide data to a table?

A. You’ve almost certainly come to the conclusion that there has got to be a better way to provide data to complex views rather than manually defining all the data within the application itself. Starting in Hour 15, “Reading and Writing Application Data,” you learn about persistent data and how it can be used within applications. This will likely become the preferred way of working with large amounts of information as you move forward in your development efforts.

Q. Can a table row have more than a single cell?

A. No, but a customized cell can be defined that presents information in a more flexible manner than the default cell. As described in the “Further Exploration” section, custom cells can be defined in Interface Builder through the UITableViewCell class.

Q. Do split view controllers have to be implemented using the Apple Master-Detail Application template?

A. Absolutely not. The template, however, provides all the right methods for many split view applications and is a great starting place for beginners.

Workshop

Quiz

1. In iOS 8, which view controller can create iPad-like applications on the iPhone?

a. Split view

b. Dual pane

c. Single universal

d. Multimodal

2. Tables are made up of objects of what type?

a. UITableViewCell

b. UITableViewRow

c. UITableViewColumn

d. UITableViewContent

3. Uniquely styled table cells have unique cell __________.

a. Purposes

b. Labels

c. Colors

d. Identifiers

4. A division within a table is known as a what?

a. Column

b. Title

c. Section

d. Divider

5. Individual cells within a table are referenced through an object of what type?

a. NSRowColumn

b. NSIndexPath

c. NSCellIndex

d. NSIndexRow

6. Which of the following methods is called when a variable has been set?

a. didStore

b. willSet

c. didSet

d. didGet

7. To access the master and detail controllers within a split view controller object, you can use which of the following variable properties?

a. start, last

b. first, end

c. start, end

d. first, last

8. The methods that provide data to a table are part of which protocol?

a. UITableViewDataSource

b. UITableViewDataProvider

c. UITableViewData

d. UITableViewDataDelegate

9. Interactions with a table cell are often handled by methods within which protocol?

a. UITableViewActions

b. UITableViewMethodDelegate

c. UITableViewClassAction

d. UITableViewDelegate

10. Which of the following templates implements a split view controller?

a. Split Application

b. Single View

c. Master-Detail

d. Tab Bar

Answers

1. A. The split view controller can be used to create iPad-like applications on the larger-screened iPhones.

2. A. The content of a table view is made of up instances of the UITableViewCell class.

3. D. Uniquely styled table cells must have unique cell identifiers.

4. C. Tables can be divided into logical groupings called sections.

5. B. The NSIndexPath object contains a row and a section that uniquely identify an individual table cell.

6. C. The didSet method is called on a variable as soon as its value has been changed.

7. D. The first and last variable properties can be used to grab the individual view controllers contained within a split view controller.

8. A. The UITableViewDataSource protocol defines the methods that provide content to a table.

9. D. The UITableViewDelegate protocol can be used to handle interactions between a user and a table cell.

10. C. The Master-Detail template provides a great starting place for implementing split view controllers.

Activities

1. Update the first tutorial to use a more expandable data structure that doesn’t rely on constants to define the number of sections or the type of sections. Your finished project should also be able to accommodate image files with arbitrary names.

2. Use IB to create and customize an instance of the UITableViewCell class.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset