A table view is a UITableView object that presents, draws, manages, and scrolls a single vertical list of rows. Each row is one element in the table. Rows can all be alike (homogeneous) or can be substantially different(heterogeneous) from one another. A table can appear as a continuous list of rows or it may organize rows into groups.
If you’ve used an iPhone, iPad, or iPod for more than a few minutes, you’ve seen table views in action. In fact, there are probably more than a few iOS app interfaces that you didn’t realize are table views. By the time you’re donewith this section, you’ll be able to spot them from a mile away.
The overall appearance of a table is set by the table style you choose when the table view is created. Its contents can be further refined by the style and layout of the individual rows. I’ll start by describing the overall table styles.
Plain Tables
The plain table style (UITableViewStylePlain) is the one you’re most likely to recognize as a table view or list. The view on the left of Figure 5-1 shows a snapshot of my Settings app. The list of regions is a plain style table view. Each row shows one region. The arrow on the right (called an accessory view) indicates that tapping that row will navigate to another list.
Figure 5-1. Plain table styles
On the right of Figure 5-1 is a plain style table with an index, a common embellishment for long lists. An index adds section labels that group similar items, and provides a quick way of jumping to a particular group in the list,using the index on the right.
Another, somewhat obscure, plain table style is the selection list style (not shown). It looks just like a plain style table with section titles, but has no index. It’s used to choose one or more options from a (potentially long) list ofoptions.
Grouped Tables
The grouped table style (UITableViewStyleGrouped) is the other table style. This style clumps sets of rows together into groups. Each group has an optional header and an optional footer, allowing you to surround the group with atitle, description, or even explanatory text. Examples of grouped tables are shown in Figure 5-2.
Figure 5-2. Grouped table style
The iPhone’s Settings app (see Figure 5-2) is built almost exclusively from table views. The title above each group is a group header. The text below is a group footer. The individual setting controls are each one row of the table.It almost doesn’t look like a table at all, but it uses the same UITableView object that Figure 5-1 does. Grouped lists do not have indexes.
The style you choose for the list sets the overall tone of your table. You then have a lot of choices when it comes to how each individual row looks.
Cell Styles
A table view cell object controls the appearance and content of each row. iOS comes with several styles of table cells:
n Default
n Subtitle
n Value1
n Value2
The default style (UITableViewCellStyleDefault) is the basic one, as shown in Figure 5-3.
Figure 5-3. Default cell style
The default style has a bold title. It may optionally include a small image, which appears on the left. The arrow, checkmark, or control on the right is called an accessory view, and I’ll talk about those shortly.
The second major cell style is the subtitle style (UITableViewCellStyleSubtitle), shown in Figure 5-4. Almost identical to the default style, it also shows a deemphasized line of text below each title. The subtitle text is also optional.If you leave out the subtitle it will look like the default style.
Figure 5-4. Subtitle cell style
The last two styles are the value1 and value2 styles (UITableViewCellStyleValue1 and UITableViewCellStyleValue2), as shown in Figure 5-5. The value1 style (on the left in Figure 5-5) is typically used to display a series of values orsettings; the title of the cell describes the value and the field on the right shows the current value.
Figure 5-5. Value1 and value2 cell styles
The alternate style, value2, puts more emphasis on the value and less on its title, as shown on the right of Figure 5-5. You’ll see this style of cell used in the Contacts app. Neither value1 nor value2 cell styles allow an image.
Cell Accessories
On the right of all standard cell styles is the optional accessory view. iOS provides three standard accessory views, as shown in Figure 5-6.
Figure 5-6. Standard accessory views
The standard accessory views are (from left to right in Figure 5-6) the disclosure indicator, detail disclosure button, and checkmark. The first two are used to indicate that tapping the row or the button will disclose—navigate to—another screen or view that displays details about that row. Nested lists are often organized this way. For example, in a table of countries each row navigates to another table listing the major cities in that country.
The disclosure indicator is not a control. It’s an indication that tapping anywhere in the row will navigate you to some additional information, as in the country/city example. The detail disclosure button, however, is a regularbutton. You must tap the accessory view button to navigate to the details. This frees the row itself to have some other purpose. The Phone app’s recent calls table works this way (see the middle of Figure 5-6); tapping a rowplaces a call to that person, while tapping the detail disclosure button navigates to their contact information.
The check mark is just that, and is used to indicate when a row has been selected or marked, for whatever purpose.
A cell’s accessory view can also be set to a control view of your choosing (such as a toggle switch).
This is very common in tables that display settings (see Figure 5-2).
Custom Cells
The two table view styles, four cell styles, and various accessory views provide a remarkable amount of flexibility. If you peruse just the Contacts, Settings, and Music apps from Apple, it’s almost stunning the number ofinterfaces (dozens, by my count) that are just different combinations of the built-in table and cell styles, with judicious use of optional images, subtitles, and accessory views.
You’ll also notice cells that don’t fit any of the styles I’ve described. There’s a wildcard in the table cell deck: you can design your own cell. A UITableCell object is a subclass of UIView. So, in theory, a table cell can contain anyview objects you want, even custom ones you’ve designed yourself using Objective-C and Interface Builder. So don’t fret if the standard styles don’t exactly fit your needs, you can always create your own.
Now that you have an idea of what’s possible, it’s time to take a closer look at how tables work.
How Table Views Work
Up to this point, every visual element in your apps has been a view object. In other words, there’s been a one-to-one relationship between what you see on your device and a UIView object in your app. Table views, however, have afew issues with that arrangement, and the table view class comes with an ingenious solution.
A table view that creates a cell object for every row runs into a number of problems when the number of rows is large. It’s not hard to imagine a contact list with several hundred names, or a music list with several thousandsongs. If a table view had to create a cell object for every single song, it would overwhelm your app, consume a ridiculous amount of memory, require a long time to create, and generally result in a sluggish and cumbersomeinterface. To avoid all of these problems, table views use some clever sleight of hand.
Table Cells and Rubber Stamps
If you’ve ever filed papers with the county clerk, or shopped in a supermarket in the days before UPC barcodes, you’re familiar with the idea of a rubber stamp that can be altered to stamp a particular date or price, using a dial ormovable segments. It would be ludicrous if your county clerk had to have a different rubber stamp for every date. Similarly, table views don’t create cell objects for every row. They create one cell object—or at least a very small number—and reuse that cell object to draw each row in the table, kind of like a rubber stamp.
Figure 5-7 shows the concept of reusing a cell object. In this figure, there are only three (principal) view objects: a UITableView object, a UITableViewCell object, and a data source object. The table view reuses the one cellobject to draw each row.
Figure 5-7. Reusable cell object
The table view does this using a delegate object, just like the delegate object you used in the Shorty app. When you create a table view object you must provide it with a data source object. Your data source object implementsspecific delegate methods that the table view object will send when it wants a cell object configured to draw a particular row.
Continuing with the rubber stamp analogy, pretend you have a table view that wants to print a list of products and their prices. It starts by handing your (data source) object the rubber stamp (cell object) and saying, “Please configure this stamp for the first product in the list.” Your object then sets the properties of the stamp (product name and price) and hands the configured stamp back to the table view. The table view uses the stamp to print thefirst row. It then turns around and repeats this process for the second row, and so on, until all of the rows have been printed.
Using this technique, a table view can draw tables that are thousands of rows tall using only a few objects. It’s fast, flexible, and wickedly efficient.
MyStuff
You’re going to create a personal inventory app named MyStuff. It’s a relatively simple app that manages a list of items you own, recording the name of each item and where you keep it (living room, kitchen, and so on).
Design
This app’s design is slightly more involved than the last two. It’s complicated, a little, by the differences between the iPhone and iPad. Apple’s Mail app looks substantially different on the iPhone versus the iPad. That’s because the iPhone only has enough screen space to comfortably display one thing at a time—either the list of messages or the content of a message. On the iPad there’s plenty of room for both. The underlying app logic is very similar, but the visual design is quite different. You’ll have to account for this in both your visual design and, to a certain extent, in your logic design. Start with the iPhone design, as shown in Figure 5-8.
Figure 5-8. Sketch of MyStuff for iPhone
The iPhone design is simple, and typical of how table views work. The main screen is a list of your items, listing their description and location. Tapping an item navigates to a second screen where you can edit those values.
The iPad design is less structured. In landscape orientation, the list of items will appear on the left, as shown in Figure 5-9. Tapping an item makes the details of that item appear on the right, where they can be changed. Inportrait orientation (not shown) the item detail consumes the screen while the list of items becomes a pop-up that the user accesses via a button in the upper-left corner of the screen.
Figure 5-9. Sketch of MyStuff for iPad
If this interface looks familiar, it’s the same one used by Apple’s Mail app. This is not a trivial interface to program, but you’re in luck; Xcode has an app template that includes all of the code needed to make this designwork. You just have to fill in the details, which is exactly what you’re going to do next.
Creating The Project
As with all apps, begin by creating a new project in Xcode. This time, choose the Master Detail iOS application template, as shown in Figure 5-10.
Figure 5-10. Creating a Master Detail app
The Master Detail template is so named because it’s what computer developers call this kind of interface. The list is your master view, displaying a summary of all of the data. The master view segues to a secondary detail view that might show more specifics about that item or provide tools for editing it.
Name the project MyStuff, give it a class prefix of MS, and make sure Use Core Data is turned off. Set the devices option to Universal. Click Next and save your new project folder somewhere.
The first thing you’ll notice is that there’s a lot of code in this project already. The Master Detail template includes all of the code needed to display a list of items, navigate to a detail interface, create new items, delete items,and handle orientation changes. The content of its table is simple NSDate objects. Your job is to replace those placeholder objects with something of substance.
Creating Your Data Model
You know that you want to display a list of “things”—the individual items that you own. And you know that each thing is going to need at least two properties, a name property and a location property, both strings. So whatobject is going to represent each thing? That’s a very important question, because this mysterious object (or objects) is what’s called your data model. Your data model comprises the objects that represent whatever concept your table view is displaying.
Clearly the Cocoa Touch framework doesn’t include such an object, so you’ll have to create one!
In the project navigator, select the MyStuff folder (not the project) towards the top of the navigator. From either the File menu, or by right/control+clicking on the folder, choose the New File ... command.
In the template assistant, select the iOS Cocoa Touch group, and then choose the Objective-C Class template. Click Next. Name the class MyWhatsit and make it a subclass of NSObject, as shown in Figure 5-11.
Figure 5-11. Creating the MyWhatsit class
Click Next, accept the default location (the MyStuff project folder), and click Create. Now you have a new class of objects in your app named MyWhatsit. Select the MyWhatsit.h interface file in the navigator. It’s pretty bleak. This is the class of objects that will represent each item that you own. Each one will need a name and a location property. Define those now by adding the following to its interface:
@property (strong,nonatomic) NSString *name;
@property (strong,nonatomic) NSString *location;
Congratulations, you now have a data model. You’ll also want to create your MyWhatsit objects with something other than nothing for a name and location, so define an “init” method that creates an object and sets both properties in a single statement. (This isn’t a requirement, but it will make some of your code easier to write.) Start by adding this method declaration to your interface file:
- (id)initWithName:(NSString*)name location:(NSString*)location;
Switch to your implementation of MyWhatsit (MyWhatsit.m). Initialization (or just “init”) methods are object constructor methods. They exist to correctly instantiate a new instance of that class. Every class inherits the plain vanilla -init method, but many classes define more elaborate initialization methods, and you’re free to create your own.
All init methods follow a well-defined pattern, or contract. Every -init method must:
1. Start by sending the appropriate -init message to its superclass.
2. Assign the returned value to self and test it for nil.
3. If self is not nil, initialize any class-specific properties.
4. Return self to the caller.
Xcode has a library of code snippets for common programming tasks, and the -init method pattern is no exception. Show the code snippet library in the utilities pane (View ➤ Utilities ➤ Show Code Snippit Library). Locatethe Objective-C init Method snippet, as shown in Figure 5-12.
Figure 5-12. Objective‑C init method snippet
Drag the snippet into the @implementation section of your MyWhatsit.m file. Replace the generic
-(id)init declaration with yours:
- (id)initWithName:(NSString*)name location:(NSString*)location
Hold down the Control key and press the / (forward slash) key. This editor shortcut jumps to the placeholder in the -init method template. Code snippets often contain placeholders that you need to replace with your code. This navigation command lets you jump right to them. Replace the placeholder with:
self.name = name; self.location = location;
When you’re finished, your implementation should look like the one in Figure 5-13.
Figure 5-13. Complete MyWhatsit implementation
Now that you have a data model, your next task is to teach the table view class how to use it.
Creating a Data Source
A table view object (UITableView) has two delegate properties. Its delegate property works just like the delegates you used in earlier chapters. The table view delegate is optional. If you choose to use one, it must be connectedto an object that adopts the UITableViewDelegate protocol.
The table view’s other delegate is its data source object. For a table view to work, you must set its dataSource property to an object that adopts the UITableViewDataSource protocol. This delegate is not optional—without it,your table won’t display anything.
The data source’s job is to provide the table view with all of the information it needs to arrange and display the contents of the table. At a minimum, your data source must:
n Report the number of rows in the table
n Configure the table view cell (rubber stamp) object for each row
A data source can also provide lots of optional information to the table view. Your data source for this app won’t need to implement any of these, but here are the kinds of things you can customize:
n Organize rows into groups
n Display section titles
n Provide an index (for indexed lists)
n Provide custom header and footer views for grouped tables
n Control which rows are selectable
n Control which rows are editable
n Control which rows are movable
As you saw in Shorty, a single class can adopt multiple protocols and can be the delegate for more than one object. In a similar vein, your view controller object can adopt both the UITableViewDelegate andUITableViewDataSource protocols and act as both the delegate and data source for a table view. This arrangement is typical in simple designs, and is exactly what’s been set up for you by the Master Details project template.Click the Main_iPhone/iPad.storyboard file, locate the Master View Controller scene, and select the table view object. Using the connections inspector, you’ll see that both its delegate and dataSource outlets have been connectedto the Master View Controller (your MSMasterViewController object).
Select the MSMasterViewController.m implementation file and take a look at the methods defined there. For a table view to work, your data source object must implement these two required methods:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *) indexPath;
The first message is sent to your data source object whenever the table view wants to know how many rows are in a particular section of your table. Remember, some tables can be grouped into sections, with each sectionhaving a different number of rows. For a simple table (like yours) there’s only one section (0), so just return the total number of rows.
You’re going to store your MyWhatsit objects in an array. One has already been defined in MSMasterViewController, but let’s rename it. At the top of the MSMasterViewController.m file, find the @interface section, select the_objects instance variable, right/control+click on it, and choose Refactor ➤ Rename ... from the pop-up menu, as shown in Figure 5-14.
Figure 5-14. Renaming a variable
Xcode’s refactoring system makes changing names, promoting and demoting methods, splitting classes, and so on relatively painless. In the rename dialog, change the name from _objects to things and click the Previewbutton. Xcode will find every reference to that variable and present a dialog showing your source before and after the proposed changes, as shown in Figure 5-15. Click the Save button. (Xcode may ask to take a snapshot ofyour project; go ahead, it’s a good idea.)
Figure 5-15. Previewing a variable name change
So why did I have you change the name of _objects? There are two reasons. The first is that code is easier to understand if the names you choose for variables have specific meanings. The variable_objects was just a little too generic for my tastes. Secondly, you shouldn’t create variable names that begin with an underscore. I know you didn’t name it, but it bothers me nonetheless.
Implementing Your Rubber Stamp
Find the -tableView:numberOfRowsInSection: method. Here’s what it looks like:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return things.count;
}
There’s nothing to change here. The method already does exactly what you need it to do: return the number of rows (MyWhatsit objects) in your table.
Move on to the -tableView:cellForRowAtIndexPath: method. The code currently looks like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
NSDate *object = things[indexPath.row]; cell.textLabel.text = [object description]; return cell;
}
This is your rubber stamp. Your object receives this message every time the table view wants to draw a row. Your job is to prepare a UITableViewCell object that will draw that row and return it to the sender. This happens in twosteps. The first step is to get the UITableViewCell object to use. Ignore that step for the moment; I’ll describe this process in the next section (“Table Cell Caching”).
The second step is to configure the cell so it draws the row correctly. The last three statements are where that happens. Right now, it expects to get an NSDate object from the array and set the label of the cell to itsdescription. This is the code you need to replace. But first, your MSMasterViewController.m file needs to know about your MyWhatsit object. At the top of the file, just below the other #import statements, add this line:
#import "MyWhatsit.h"
Now go back to your -tableView:cellForRowAtIndexPath: method and replace the last three statements in the method with:
MyWhatsit *thing = things[indexPath.row]; cell.textLabel.text = thing.name; cell.detailTextLabel.text = thing.location; return cell;
Now your rubber stamp gets the MyWhatsit object for the row to be drawn (from the indexPath object) and stores it in the thing variable. It then uses the name and location properties to set the textLabel (title) and detailTextLabel
(subtitle) of the cell.















No comments:
Post a Comment