Tải bản đầy đủ - 0 (trang)
Chapter 11. Table Views and Collection Views

Chapter 11. Table Views and Collection Views

Tải bản đầy đủ - 0trang

These questions are asked of the view’s data source, which is an Objective-C object that

conforms to the view’s data source protocol. The data source protocol differs based on

the type of view that you’re using.

There are other questions that a data view may need to know the answer to, including

“How tall should each row in the list be?” and “How many sections should the list

contain?” These questions are also answered by the data source, but the view can fall

back to some default values in case the data source isn’t able to provide this information.

Sometimes displaying information is all you want, but your application usually needs

to respond to the user interacting with the view’s content. The specific things that the

user can do vary depending on the platform, the kind of data view, and how you’ve

configured the view. Some possible interactions include:

• Clicking (or tapping) on an item

• Rearranging content

• Deleting or modifying content

These actions are sent by the view to its delegate.

Table Views

Table views are designed for showing lists of information. On OS X, a table view shows

data with multiple columns, which can be rearranged and resized, and is generally used

to show data. On iOS, table views only show one column and are useful for any kind of

vertically scrolling list, as seen in the Settings application.

UITableView on iOS

Table views are implemented on iOS using the UITableView class. This is one of the

most versatile view classes on iOS: with it, you can create interfaces that range from

simple lists of data to complex, multi-part, scalable interfaces.

On iOS, the term “table view” is somewhat of a misnomer. The word “table” usually

brings to mind a grid with multiple rows and columns, but on iOS the table view is

actually a single column with multiple rows. The reason for this is that the size of the

iPhone’s screen is too narrow for more than one column to make sense, but the API

design for UITableViewController was based on NSTableViewController, which we’ll

discuss later in this chapter.

Table views on iOS present a scrolling list of table view cells, which are views that can

contain any data you like. UITableView is designed for speed: one of the most common



Chapter 11: Table Views and Collection Views


gestures that the user performs on an iOS device is to flick a finger up and down a

scrolling list, which means that the application needs to be able to animate the scrolling

of that list at high frame rates (ideally, 60 frames per second, which is the maximum

frame rate of iOS).

Sections and Rows

Table views can be divided into multiple sections, each of which contains one or more

rows. Sections allow you to divide your content in a manner that makes sense to you.

For example, the Contacts application uses a table view that divides rows by surname,

and the Settings application uses a table view that divides rows into categories.

Because table views are divided into sections, specific locations in the table view are

identified not by row, but by index path. An index path is nothing more complex than

a section number and a row number, and is represented using the NSIndexPath class:

NSIndexPath* indexPath = [NSIndexPath indexPathForRow:2 inSection:1];

(Note that you don’t usually create NSIndexPaths yourself—this example just shows how

they’re composed.)

Let’s imagine that we’ve got a table view that’s divided into two sections: the first section

has two rows, and the second section has three (Figure 11-1).

Figure 11-1. A table view, divided into sections

Using index paths, you can refer to the very first cell as section 0, row 0. The second cell

is section 0, row 1, and the third cell is section 1, row 0. This allows cells to be numbered

independently of their sections, which can be very handy indeed.

UITableView on iOS




Table View Controllers

If you add a UITableView to an interface without doing any additional work, you’ll see

an empty list. By default, UITableViews rely on a data source object to provide them with

information on what content to show.

Any object can be a data source for a UITableView; the only requirement is that it must

conform to the UITableViewDatasource protocol [see “Protocols” (page 29)].

The object is almost always a UIViewController, and almost always the view controller

of the view that contains the table view. There’s nothing stopping you from doing it

differently, though.

The two critical methods that the UITableViewDatasource protocol defines are:


-(UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath

The first, numberOfRowsInSection:, returns the number of rows in the

specified table section [see “Sections and Rows” (page 177)]. The second, cellFor

RowAtIndexPath:, returns a UITableViewCell for the specified index path.

For example, here’s how to indicate that every section in the table has two rows:

- (NSInteger)numberOfRowsInSection:(NSInteger)section {

return 2;


This method is the easier of the two. Here’s an example of an implementation of table

View:cellForRowAtIndexPath:; we’ll talk about exactly what it does in the next section.

- (UITableViewCell*) tableView:(UITableView*) tableView

cellForRowAtIndexPath:(NSIndexPath*) indexPath {

UITableViewCell* cell = [tableView


cell.textLabel.text = @"Hello";

return cell;


Table View Cells

A table view cell represents a single item in the table view. Table view cells are UITable

ViewCells, a subclass of UIView. Just like any other UIView, table view cells can have

any other UIViews included as subviews.



Chapter 11: Table Views and Collection Views


When the table view needs to show data, it needs the answer to two questions: how many

rows are there to show, and what should be shown for each row. The first question is

answered by the numberOfRowsInSection: method; the second is answered by table


cellForRowAtIndexPath: is called for every visible row in the table view as it comes into

view. This last part is important, because it enables the table view to not have to worry

about displaying content that isn’t visible. If, for example, you have a table view that

contains a thousand objects, fewer than ten of those objects are likely to be visible.

Because it makes no sense for the table view to attempt to display table view cells for

rows that may never be shown, tableView:cellForRowAtIndexPath: is called only as

a cell is about to come onto the screen.

tableView:cellForRowAtIndexPath: is responsible for returning a configured UI

TableViewCell. “Configured,” in this case, means making sure that the table view cell

is displaying the right content. That content depends on what the table view is being

used for: if you’re making a shopping list application, each table view cell would contain

an item in the shopping list.

Cell reuse

As the user scrolls the table view, some items in the list will go off screen while others

come on screen. When a table view cell in the list scrolls off screen, it is removed from

the table view and placed in a reuse queue. This reuse queue stores UITableViewCell

objects that have been created but are not currently visible. When a cell is scrolled into

view, the table view retrieves an already created UITableViewCell object from the reuse


The advantage of this method is that the time taken to allocate and set up a new object

is completely removed. All memory allocations take time, and if the user is quickly

scrolling through a long list, he would see a noticeable pause as each new cell appeared.

UITableViewCell objects are automatically placed into the reuse queue by the table view

as the cells scroll off screen; when the table view’s data source receives the table

View:cellForRowAtIndexPath: message, it fetches a cell from the reuse queue and

prepares that, rather than creating an entirely new cell.

A table view can have many different kinds of cells—for example, you might have a table

view with two sections that show entirely different cells in each section. However, there’s

only one tableView:cellForRowAtIndexPath: method, which is called for all rows in

all sections. To differentiate, you can use the index path that is passed to this method to

figure out which section and row the table view is wanting a cell for.

UITableView on iOS




Anatomy of a UITableViewCell

A table view cell is a UIView, which can contain any additional UIViews that you want

to include. In addition to this flexibility, UITableViewCell objects also support a few

basic styles, which are similar to the table view cells seen in other parts of iOS. There

are four basic table view styles:


A black, bold, left-aligned text label, with an optional image view. As the name

suggests, this is the default style for table view cells.

Value 1

A black, left-aligned text label, with a smaller, blue-colored, right-aligned text label

on the righthand side. This cell style can be seen in the Settings application.

Value 2

A blue, right-aligned label on the lefthand side, with a black, left-aligned label on

the righthand side. This cell style can be seen in the Phone and Contacts applica‐



A black, left-aligned label, with a smaller, gray label underneath it. This cell style

can be seen in the Music application.

The common theme is that all table view cells have at least one primary text label, and

optionally a secondary text label and an image view. These views are UILabel and

UIImageView objects, and can be accessed through the textLabel, detailTextLabel,

and imageView properties of the table view cell.

Preparing table views in Interface Builder

Prior to iOS 5, constructing table views and table view cells was a largely programmatic

affair, with developers writing code that manually instantiated and laid out the contents

of any nonstandard table view cells in code. This wasn’t a great idea, since layout code

can get tricky. So, from iOS 5 onward, table views and their cells can be designed entirely

in Interface Builder.

When you add a table view to an interface, you can also create prototype cells. The

contents of these cells can be designed by you and completely customized (from chang‐

ing the colors and fonts to completely changing the layout and providing a custom

subclass of UITableViewCell). These prototype cells are marked with a cell identifier,

which allows your code to create instances of the prototypes.

Analyzing tableView:cellForRowAtIndexPath:

With all of the above in mind, we can now take a closer look at the tableView:cell

ForRowAtIndexPath: implementation that we looked at earlier. Here it is again:



Chapter 11: Table Views and Collection Views


- (UITableViewCell*) tableView:(UITableView*) tableView

cellForRowAtIndexPath:(NSIndexPath*) indexPath {

// 1

UITableViewCell* cell = [tableView


// 2

cell.textLabel.text = @"Hello";

// 3

return cell;


The method performs three actions:

1. The table view is asked to dequeue a table view cell that has a cell identifier of

MyCell. This causes the table view to either create an instance of the prototype cell

that has this identifier, or dequeue one from the reuse queue if a cell with this

identifier has been previously created and is not currently visible.

2. The cell’s primary text label is set to display the text Hello.

3. Finally, the cell is returned to the table view, which will display it to the user.

Responding to actions

The most common thing that the user does with table view cells is to tap them. When

this happens, the table view will contact its delegate and inform it that a cell was selected.

An object must conform to the UITableViewDelegate protocol in order to be a delegate.

The table view’s delegate can be different from its data source, but in practice the delegate

is the same object as the data source (that is, the view controller that manages the table

view conforms to both the UITableViewDelegate and UITableViewDatasource proto‐


There are several methods that UITableViewDelegate specifies, all of which are

optional. The most important and commonly used method is tableView:didSe

lectRowAtIndexPath:, which is called when a row is tapped. This is the delegate’s op‐

portunity to perform an action like moving to another screen.

- (void)tableView:(UITableView *)tableView

didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

// The table view cell at 'indexPath' got selected


UITableView on iOS




Implementing a Table View

To put it all together, we’ll build a simple table view application that displays the contents

of an array of NSString objects.

1. Create a new iOS application named iOSTableView. Make it a single-view applica‐

tion for the iPhone.

2. Delete the ViewController.h and ViewController.m files. We’ll be creating replace‐

ments for them shortly.

3. Create and set up the table view. In this example, the view controller will be a

UITableViewController, which is a subclass of UIViewController that manages

a single UITableView.

Open MainStoryboard.storyboard and delete the existing view controller. Then drag

in a Table View Controller from the Objects Library.

4. Set up the prototype cell. By default, a UITableViewController that’s been dragged

in to the storyboard contains a single prototype cell. We’re going to configure that

to be the cell that we want.

Select the single cell that appears at the top of the table view.

Make sure the Attributes inspector is open, and change its identifier to StringCell.

Change the cell’s style from Custom to Basic.

The table view is fully configured; it’s now time to write the code that will provide the

table view with the information it needs.

5. Create the new table view controller class. Create a new Objective-C class by choosing

File→New→File… and selecting “Objective-C class.” Name it TableViewControl

ler and make it a subclass of UITableViewController.

6. Make the table view controller use the new class. Go back to the storyboard and select

the view controller.

Note that clicking on the table view won’t select the view controller—it’ll select the

table view. You can select the table view controller itself from the Outline view on

the lefthand side of the Interface Builder.

Go to the Identity inspector and change the class from UITableViewController to


7. Open TableViewController.h and add the array of strings.

We can now write the code that drives the table view. First, we need to create an

NSArray that contains the NSString objects that will be displayed.

Make the class extension at the top of TableViewController.m look like this:



Chapter 11: Table Views and Collection Views


@interface TableViewController () {

NSArray* data;



Next, add the following line of code to the viewDidLoad method.

data = @[@"Once", @"upon", @"a", @"time"];

8. Make the table view data source return one section. The table view will contain one

section, and this section will contain as many rows as there are entries in the data


To determine how many sections are present, the table view sends its data source

object the numberOfSectionsInTableView: message. This method is already im‐

plemented in the template code, but returns zero. We just need to change this to

return 1.

Find the numberOfSectionsInTableView: method in TableViewController.m

and replace it with the following code:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView


return 1;


9. Make the table view data source indicate the correct number of rows for the section.

We need to tell the table view that the section has as many rows as there are objects

in the data array. This is handled by the tableView:numberOfRowsInSection:


Find the tableView:numberOfRowsInSection: method in TableViewController.m

and replace it with the following code:

- (NSInteger)tableView:(UITableView *)tableView



return data.count;


10. Implement the tableView:cellForRowAtIndexPath: method. We need to prepare

the table view cells for each of the rows that the table view will ask for.

Find the tableView:cellForRowAtIndexPath: method in TableViewController.m

and replace it with the following code:

- (UITableViewCell *)tableView:(UITableView *)tableView

cellForRowAtIndexPath:(NSIndexPath *)indexPath


static NSString *CellIdentifier = @"StringCell";

UITableViewCell *cell = [tableView



UITableView on iOS




NSString* string = data[indexPath.row];

cell.textLabel.text = string;

return cell;


11. Implement the tableView:didSelectRowAtIndexPath: method. Finally, we’ll make

the code log the string corresponding to the text that was selected.

Find the tableView:didSelectRowAtIndexPath: method in TableViewControl

ler.m and replace it with the following code:

- (void)tableView:(UITableView *)tableView

didSelectRowAtIndexPath:(NSIndexPath *)indexPath


NSLog(@"Selected %@", data[indexPath.row]);


NSTableView on OS X

The process of displaying tables of data on OS X is slightly more complex than on iOS.

Tables on OS X are capable of displaying multiple columns of data, which can be

rearranged and resorted by the user. Table views on OS X are instances of the NSTable

View class. However, the fundamental idea behind table views on OS X is the same as

on iOS—a table view uses a data source object to determine how many rows exist and

what content should be shown for each row.

The only significant difference in terms of programming for NSTableView is that the

method that returns the content that should be shown for a table view cell needs to take

into account both the row number and column for the data.

The method for returning the view that should be shown in a cell is tableView:view

ForTableColumn:row:. This method’s parameters are the NSTableView that wants to

show content, and the column and row number that are being displayed. The row num‐

ber is represented as a simple integer, while the table column is an NSTableColumn. This

is because columns can be rearranged, and it therefore doesn’t make sense to have “col‐

umn numbers.” Rather, NSTableColumn objects have identifiers, which are used by your

code to figure out what specific piece of information needs to be shown.

To demonstrate how table views work on OS X, we’ll build an application that displays

multiple columns of data. This app will display a list of songs, along with their running


1. Create the project. Create a new Cocoa application called CocoaTableView.



Chapter 11: Table Views and Collection Views


2. Create the Song class. The first thing we’ll do is create the data source. Each song

that the application displays will be an instance of the class Song, which we’ll create

ourselves. Each Song object has a title string, as well as a duration represented as

an NSTimeInterval (which is just another way of saying float—it’s a typedef de‐

fined by Cocoa).

To create the class, go to File→New→File… and choose Objective-C class. Create a

new class called Song and make it a subclass of NSObject.

Once it’s been created, open Song.h and make its @interface look like the following


@interface Song : NSObject

@property (strong) NSString* title;

@property (assign) NSTimeInterval duration;


You don’t need to do anything in Song.m, because this class only

contains properties and no methods.

3. Add the songs and songsController properties to AppDelegate. Next, we’ll make

AppDelegate store a list of Song objects. This list will be an NSMutableArray, which

is managed by an NSArrayController. This controller will be used as part of the

bindings used to drive the table view.

Open AppDelegate.h and add the following properties to the @interface of App


@property (strong) NSMutableArray* songs;

@property (strong) IBOutlet NSArrayController *songsController;

4. Populate the songs list. Finally, we need to make the object populate this list when

it appears.

Open AppDelegate.m and add the following method to the @implementation of


@implementation AppDelegate

- (void)awakeFromNib {

self.songs = [NSMutableArray array];

Song* aSong;

NSTableView on OS X




aSong = [[Song alloc] init];

aSong.title = @"Gaeta's Lament";

aSong.duration = 289;

[self.songsController addObject:aSong];

aSong = [[Song alloc] init];

aSong.title = @"The Signal";

aSong.duration = 309;

[self.songsController addObject:aSong];

aSong = [[Song alloc] init];

aSong.title = @"Resurrection Hub";

aSong.duration = 221;

[self.songsController addObject:aSong];

aSong = [[Song alloc] init];

aSong.title = @"The Cult of Baltar";

aSong.duration = 342;

[self.songsController addObject:aSong];


Bonus points for those who get the reference!

We’ll now prepare the interface for the application.

5. Add the array controller. Drag an array controller into the outline.

Open the Bindings inspector, and bind the content array to the app delegate. Set

the model key path to self.songs.

Hold down the Control key and drag from the app delegate to the array controller.

Choose songsController from the menu that appears.

6. Create the table view. Open MainMenu.xib and select the window. It’s empty, but

we’ll soon fix that.

Drag a table view from the Objects Library into the window. Make it fill the window.

Select the table header view at the top of the table view. Double-click the first col‐

umn’s header and rename it Title. Rename the second column header Duration.



Chapter 11: Table Views and Collection Views


Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Chapter 11. Table Views and Collection Views

Tải bản đầy đủ ngay(0 tr)