Chapter 8. Table Views and Collection Views

I’m gonna ask you the three big questions. — Go ahead. — Who made you? — You did. — Who owns the biggest piece of you? — You do. — What would happen if I dropped you? — I’d go right down the drain.

Dialogue by Garson Kanin and Ruth Gordon, Pat and Mike

A table view (UITableView) is a vertically scrolling UIScrollView (Chapter 7) containing a single column of rectangular cells (UITableViewCell, a UIView subclass). It is a keystone of Apple’s strategy for making the small iPhone screen useful and powerful, and has three main purposes:

Presentation of information
The cells typically contain text, which the user can read. The cells are usually quite small, in order to maximize the quantity appearing on the screen at once, so this text is often condensed, truncated, or simplified.
Selection
A table view can provide the user with a column of choices. The user chooses by tapping a cell, which selects the cell; the app responds appropriately to that choice.
Navigation
The appropriate response to the user’s choosing a cell is often navigation to another interface. This might be done, for example, through a presented view controller or a navigation interface (Chapter 6). An extremely common configuration is a master–detail interface, where the master view is a table view within a navigation interface: the user taps a table view cell to navigate to the details about that cell. This is one reason why truncation of text in a table view cell is acceptable: the detail view contains the full information.

In addition to its column of cells, a table view can be extended by a number of other features that make it even more useful and flexible:

Figure 8-1 illustrates four variations of the table view:

Four table view variations
Figure 8-1. Four table view variations

Table view cells, too, can be extremely flexible. Some basic cell formats are provided, such as a text label along with a small image view, but you are free to design your own cell as you would any other view. There are also some standard interface items that are commonly used in a cell, such as a checkmark to indicate selection or a right-pointing chevron to indicate that tapping the cell navigates to a detail view.

It would be difficult to overestimate the importance of table views. An iOS app without a table view somewhere in its interface would be a rare thing, especially on the small iPhone screen. I’ve written apps consisting almost entirely of table views. Indeed, it is not uncommon to use a table view even in situations that have nothing particularly table-like about them, simply because it is so convenient.

For example, in one of my apps I want the user to be able to choose between three levels of difficulty and two sets of images. In a desktop application I’d probably use radio buttons; but there are no radio buttons among the standard iOS interface objects. Instead, I use a grouped table view so small that it doesn’t even scroll. This gives me section headers, tappable cells, and a checkmark indicating the current choice (Figure 8-2).

A grouped table view as an interface for choosing options
Figure 8-2. A grouped table view as an interface for choosing options

There is a UIViewController subclass, UITableViewController, dedicated to the presentation of a table view. You never really need to use a UITableViewController; it’s a convenience, but it doesn’t do anything that you couldn’t do yourself by other means. Here’s some of what using a UITableViewController gives you:

Beginners may be surprised to learn that a table view’s structure and contents are generally not configured in advance. Rather, you supply the table view with a data source and a delegate (which will often be the same object), and the table view turns to these in real time, as the app runs, whenever it needs a piece of information about its structure and contents.

This architecture is part of a brilliant strategy to conserve resources. Imagine a long table consisting of thousands of rows. It must appear, therefore, to consist of thousands of cells as the user scrolls. But a cell is a UIView and is memory-intensive; to maintain thousands of cells internally would put a terrible strain on memory. Therefore, the table typically maintains only as many cells as are showing simultaneously at any one moment (about ten, let’s say). As the user scrolls to reveal new cells, those cells are created on the spot; meanwhile, the cells that have been scrolled out of view are permitted to die.

This sounds ingenious but a bit wasteful, and possibly time-consuming. Wouldn’t it be even cleverer if, instead of letting a cell die as it is scrolled out of view, it were whisked around to the other side and used again as one of the cells being scrolled into view? Yes, and in fact that’s exactly what you’re supposed to do. You do it by assigning each cell a reuse identifier.

As cells with a given reuse identifier are scrolled out of view, the table view maintains a bunch of them in a pile. As cells are scrolled into view, you ask the table view for a cell from that pile, specifying it by means of the reuse identifier. The table view hands an old used cell back to you, and now you can configure it as the cell that is about to be scrolled into view. Cells are thus reused to minimize not only the number of actual cells in existence at any one moment, but the number of actual cells ever created. A table of 1000 rows might very well never need to create more than a dozen cells over the entire lifetime of the app.

Your code must be prepared, on demand, to supply the table with pieces of requested data. Of these, the most important is the cell to be slotted into a given position. A position in the table is specified by means of an index path (NSIndexPath), a class used here to combine a section number with a row number, and is often referred to simply as a row of the table. Your data source object may at any moment be sent the message tableView:cellForRowAtIndexPath:, and must respond by returning the UITableViewCell to be displayed at that row of the table. And you must return it fast: the user is scrolling now, so the table needs the next cell now.

In this section, I’ll discuss what you’re going to be supplying — the table view cell. After that, I’ll talk about how you supply it.

The simplest way to obtain a table view cell is to start with one of the four built-in table view cell styles. To create a cell using a built-in style, call initWithStyle:reuseIdentifier:. The reuseIdentifier: is what allows cells previously assigned to rows that are no longer showing to be reused for cells that are; it will usually be the same for all cells in a table. Your choices of cell style are:

To experiment with the built-in cell styles, do this:

To get our table view into the interface, import "RootViewController.h" into AppDelegate.m, and add this line to AppDelegate’s application:didFinishLaunchingWithOptions: at the override point:

self.window.rootViewController = [RootViewController new];

Now modify the RootViewController class (which comes with a lot of templated code), as in Example 8-1. Run the app to see the world’s simplest table (Figure 8-3).

The key parts of the code are:

1

Our table will have one section.

2

Our table will consist of 20 rows. Having multiple rows will give us a sense of how our cell looks when placed next to other cells.

3

This is where you specify the built-in table view cell style you want to experiment with.

4

At this point in the code you can modify characteristics of the cell (cell) that are to be the same for every cell of the table. For the moment, I’ve symbolized this by assuming that every cell’s text is to be the same color.

5

We now have the cell to be used for this row of the table, so at this point in the code you can modify characteristics of the cell (cell) that are unique to this row. I’ve symbolized this by appending successive numbers to the text of each row. Of course, that’s completely unrealistic; but that’s just because we’re only beginners. In real life the different cells would reflect meaningful data. I’ll talk about that later in this chapter.

Now you can experiment with your cell’s appearance by tweaking the code and running the app. Feel free to try different built-in cell styles in the place where we are now specifying UITableViewCellStyleDefault.

The flexibility of each built-in style is based mostly on the flexibility of UILabels. Not everything can be customized, because after you return the cell some further configuration takes place, which may override your settings. For example, the size and position of the cell’s subviews are not up to you. (I’ll explain, a little later, how to get around that.) But you get a remarkable degree of freedom. Here are a few basic UILabel properties for you to play with now (by customizing cell.textLabel), and I’ll talk much more about UILabels in Chapter 10:

text
The string shown in the label.
textColor, highlightedTextColor

The color of the text. The highlightedTextColor applies when the cell is highlighted or selected (tap on a cell to select it).

In earlier versions of iOS, if you didn’t set the highlightedTextColor, the label would choose its own variant of the textColor when the cell was highlighted or selected. In iOS 7, that’s no longer the case; the textColor is used unless you set the highlightedTextColor explicitly.

textAlignment
How the text is aligned; some possible choices are NSTextAlignmentLeft, NSTextAlignmentCenter, and NSTextAlignmentRight.
numberOfLines
The maximum number of lines of text to appear in the label. Text that is long but permitted to wrap, or that contains explicit linefeed characters, can appear completely in the label if the label is tall enough and the number of permitted lines is sufficient. 0 means there’s no maximum.
font

The label’s font. You could reduce the font size as a way of fitting more text into the label. A font name includes its style. For example:

cell.textLabel.font = [UIFont fontWithName:@"Helvetica-Bold" size:12.0];
shadowColor, shadowOffset
The text shadow. Adding a little shadow can increase clarity and emphasis for large text.

You can also assign the image view (cell.imageView) an image. The frame of the image view can’t be changed, but you can inset its apparent size by supplying a smaller image and setting the image view’s contentMode to UIViewContentModeCenter. It’s probably a good idea in any case, for performance reasons, to supply images at their drawn size and resolution rather than making the drawing system scale them for you (see the last section of Chapter 7). For example:

CGFloat side = 30;
UIImage* im = [UIImage imageNamed:@"smiley"];
UIGraphicsBeginImageContextWithOptions(CGSizeMake(side,side), YES, 0);
[im drawInRect:CGRectMake(0,0,side,side)];
UIImage* im2 = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
cell.imageView.image = im2;
cell.imageView.contentMode = UIViewContentModeCenter;

The cell itself also has some properties you can play with:

accessoryType

A built-in type of accessory view, which appears at the cell’s right end. For example:

cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
accessoryView

Your own UIView, which appears at the cell’s right end (overriding the accessoryType). For example:

UIButton* b = [UIButton buttonWithType:UIButtonTypeSystem];
[b setTitle:@"Tap Me" forState:UIControlStateNormal];
[b sizeToFit];
// ... also assign button a target and action ...
cell.accessoryView = b;
indentationLevel, indentationWidth
These properties give the cell a left margin, useful for suggesting a hierarchy among cells. You can also set a cell’s indentation level in real time, with respect to the table row into which it is slotted, by implementing the delegate’s tableView:indentationLevelForRowAtIndexPath: method.
separatorInset
A new iOS 7 feature. Only the left and right insets matter. The default is a left inset of 15, though if you don’t set it explicitly, the built-in table view cell styles may shift it. This property affects both the drawing of the separator between cells and the indentation of content of the built-in cell styles.
selectionStyle

How the background looks when the cell is selected. The default, new in iOS 7, is solid gray (UITableViewCellSelectionStyleDefault), or you can choose UITableViewCellSelectionStyleNone.

(The blue and gray gradient backgrounds designated by UITableViewCellSelectionStyleBlue and UITableViewCellSelectionStyleGray in iOS 6 and before are now abandoned, and are treated as equivalent to UITableViewCellSelectionStyleDefault.)

backgroundColor
backgroundView
selectedBackgroundView

What’s behind everything else drawn in the cell. The selectedBackgroundView is drawn in front of the backgroundView (if any) when the cell is selected, and will appear instead of whatever the selectionStyle dictates. The backgroundColor is behind the backgroundView. (Thus, if both the selectedBackgroundView and the backgroundView have some transparency, both of them and the backgroundColor can appear composited together when the cell is selected.)

There is no need to set the frame of the backgroundView and selectedBackgroundView; they will be resized automatically to fit the cell.

multipleSelectionBackgroundView
If defined (not nil), and if the table’s allowsMultipleSelection (or, if editing, allowsMultipleSelectionDuringEditing) is YES, used instead of the selectedBackgroundView when the cell is selected.

In this example, we set the cell’s backgroundView to display an image with some transparency at the outside edges, so that the backgroundColor shows behind it, and we set the selectedBackgroundView to an almost transparent blue rectangle, to darken that image when the cell is selected (Figure 8-4):

cell.textLabel.textColor = [UIColor whiteColor];
UIImageView* v = [UIImageView new]; // no need to set frame
v.contentMode = UIViewContentModeScaleToFill;
v.image = [UIImage imageNamed:@"linen.png"];
cell.backgroundView = v;
UIView* v2 = [UIView new]; // no need to set frame
v2.backgroundColor = [[UIColor blueColor] colorWithAlphaComponent:0.2];
cell.selectedBackgroundView = v2;
cell.backgroundColor = [UIColor redColor];

If those features are to be true of every cell ever displayed in the table, then that code should go in the spot numbered 4 in Example 8-1; there’s no need to waste time doing the same thing all over again when an existing cell is reused.

Finally, here are a few properties of the table view itself worth playing with:

rowHeight
The height of a cell. A taller cell may accommodate more information. You can also change this value in the nib editor; the table view’s row height appears in the Size inspector. The cell’s subviews have their autoresizing set so as to compensate correctly. You can also set a cell’s height in real time by implementing the delegate’s tableView:heightForRowAtIndexPath: method; thus a table’s cells may differ from one another in height (more about that later in this chapter).
separatorStyle
separatorColor
separatorInset

These can also be set in the nib. The table’s separatorInset is adopted by individual cells that don’t have their own explicit separatorInset. Separator styles are:

(The former UITableViewCellSeparatorStyleSingleLineEtched style is abandoned in iOS 7, and equates to None.)

backgroundColor, backgroundView
What’s behind all the cells of the table; this may be seen if the cells have transparency, or if the user scrolls the cells beyond their limit. The backgroundView is drawn on top of the backgroundColor.
tableHeaderView, tableFooterView

Views to be shown before the first row and after the last row, respectively (as part of the table’s scrolling content). Their background color is, by default, the background color of the table, but you can change that. You dictate their heights; their widths will be dynamically resized to fit the table. The user can, if you like, interact with these views (and their subviews); for example, a view can be (or can contain) a UIButton.

You can alter a table header or footer view dynamically during the lifetime of the app; if you change its height, you must set the corresponding table view property afresh to notify the table view of what has happened.

In tableView:cellForRowAtIndexPath:, there are actually two possible ways to obtain a reusable cell:

If you use the second method, which was introduced in iOS 6, you pass along as the second argument the same indexPath: value that you already received. I prefer the second method, and will use it from now on. It has three advantages:

The result is never nil
Unlike dequeueReusableCellWithIdentifier:, the value returned by dequeueReusableCellWithIdentifier:forIndexPath: is never nil. If there is a free reusable cell with the given identifier, it is returned. If there isn’t, a new one is created for you. Step 3 of Example 8-1 can thus be eliminated.
The row height is known earlier
Unlike dequeueReusableCellWithIdentifier:, the cell returned by dequeueReusableCellWithIdentifier:forIndexPath: has its final bounds. That’s possible because you’ve passed the index path as an argument, so the runtime knows this cell’s ultimate destination within the table, and has already consulted the table’s rowHeight or the delegate’s tableView:heightForRowAtIndexPath:. This makes laying out the cell’s contents much easier.
The identifier is consistent
A danger with dequeueReusableCellWithIdentifier: is that you may accidentally pass an incorrect reuse identifier, or nil, and end up not reusing cells. With dequeueReusableCellWithIdentifier:forIndexPath:, that can’t happen.

Before you call dequeueReusableCellWithIdentifier:forIndexPath: for the first time, you must register with the table itself. You do this by calling registerClass:forCellReuseIdentifier:. This associates a class (which must be UITableViewCell or a subclass thereof) with a string identifier. That’s how dequeueReusableCellWithIdentifier:forIndexPath: knows what class to instantiate when it creates a new cell for you: you pass an identifier, and you’ve already told the table what class it signifies. The only cell types you can obtain are those for which you’ve registered in this way; if you pass a bad identifier, the app will crash (with a helpful log message).

This is a very elegant mechanism. It also raises some new questions:

When should I call registerClass:forCellReuseIdentifier:?

Call it early, before the table view starts generating cells. viewDidLoad is a good place:

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.tableView registerClass:[UITableViewCell class]
           forCellReuseIdentifier:@"Cell"];
}
How do I specify a built-in table view cell style?

We are no longer calling initWithStyle:reuseIdentifier:, so where do we make our choice of built-in cell style? The default cell style is UITableViewCellStyleDefault, so if that’s what you wanted, the problem is solved. Otherwise, subclass UITableViewCell and override initWithStyle:reuseIdentifier: to substitute the cell style you’re after (passing along the reuse identifier you were handed).

For example, let’s call our UITableViewCell subclass MyCell. So we now specify [MyCell class] in our call to registerClass:forCellReuseIdentifier:. MyCell’s initializer looks like this:

- (id)initWithStyle:(UITableViewCellStyle)style
        reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:UITableViewCellStyleSubtitle // or whatever
                reuseIdentifier:reuseIdentifier];
    if (self) {
        // ...
    }
    return self;
}
How do I know whether the returned cell is new or reused?

It’s important to know this, because there needs to be a way distinguish between step 4 of Example 8-1 (configurations to apply once and for all to a new cell) and step 5 (configurations that differ for each row). It’s up to you, when performing one-time configuration on a cell, to give that cell some distinguishing mark that you can look for later to determine whether a cell requires one-time configuration.

For example, if every cell is to have a green background, there is no point giving every cell returned by dequeueReusableCellWithIdentifier:forIndexPath: a green background; the reused cells already have one. Now, however, no cell is nil, and we are never instantiating the cell ourselves. So how will we know which ones need to be given a green background? It’s easy: they are the ones without a green background:

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell =
        [tableView dequeueReusableCellWithIdentifier:@"Cell"
            forIndexPath:indexPath];
    if (![cell.backgroundColor isEqual:[UIColor greenColor]]) {
        // do one-time configurations
        cell.textLabel.textColor = [UIColor redColor];
        cell.backgroundColor = [UIColor greenColor];
    }
    // do individual cell configurations
    cell.textLabel.text =
        [NSString stringWithFormat:@"Hello there! %d", indexPath.row];
    return cell;
}

Cell class registration with registerClass:forCellReuseIdentifier: is also consistent with ways of generating a cell when you don’t want one of the four built-in table view cell styles, or when you want to design the cell with the nib editor, as I’ll now proceed to explain.

Custom Cells

The built-in cell styles give the beginner a leg up in getting started with table views, but there is nothing sacred about them, and soon you’ll probably want to transcend them, putting yourself in charge of how a table’s cells look and what subviews they contain. The thing to remember is that the cell has a contentView property, which is one of its subviews; things like the accessoryView are outside the contentView. All your customizations must be confined to subviews of the contentView; this allows the cell to continue working correctly.

There are four possible approaches:

I’ll illustrate each approach.

Instead of modifying the existing default subviews, you can add completely new views to each UITableViewCell’s content view. This has some great advantages over the preceding technique. We won’t be fighting the runtime, so we can make our changes in tableView:cellForRowAtIndexPath:, and we can assign a frame or constraints. Here are some things to keep in mind:

I’ll rewrite the previous example (Figure 8-5) to use this technique. We are no longer using a UITableViewCell subclass; the registered cell class is UITableViewCell itself. If this is a new cell, we add the subviews and assign them tags. If this is a reused cell, we don’t add the subviews (the cell already has them), and we use the tags to refer to the subviews:

- (UITableViewCell *)tableView:(UITableView *)tableView
        cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell =
        [tableView dequeueReusableCellWithIdentifier:@"Cell"
            forIndexPath:indexPath];
    if (![cell viewWithTag:1]) {
        UIImageView* iv = [UIImageView new];
        iv.tag = 1;
        [cell.contentView addSubview:iv];
        UILabel* lab = [UILabel new];
        lab.tag = 2;
        [cell.contentView addSubview:lab];
        // position using constraints
        NSDictionary* d = NSDictionaryOfVariableBindings(iv, lab);
        iv.translatesAutoresizingMaskIntoConstraints = NO;
        lab.translatesAutoresizingMaskIntoConstraints = NO;
        // image view is vertically centered
        [cell.contentView addConstraint:
         [NSLayoutConstraint
          constraintWithItem:iv attribute:NSLayoutAttributeCenterY
          relatedBy:0
          toItem:cell.contentView attribute:NSLayoutAttributeCenterY
          multiplier:1 constant:0]];
        // it's a square
        [cell.contentView addConstraint:
         [NSLayoutConstraint
          constraintWithItem:iv attribute:NSLayoutAttributeWidth
          relatedBy:0
          toItem:iv attribute:NSLayoutAttributeHeight
          multiplier:1 constant:0]];
        // label has height pinned to superview
        [cell.contentView addConstraints:
         [NSLayoutConstraint
          constraintsWithVisualFormat:@"V:|[lab]|"
          options:0 metrics:nil views:d]];
        // horizontal margins
        [cell.contentView addConstraints:
         [NSLayoutConstraint
          constraintsWithVisualFormat:@"H:|-15-[lab]-15-[iv]-15-|"
          options:0 metrics:nil views:d]];
    }
    UILabel* lab = (UILabel*)[cell.contentView viewWithTag: 2];
    UIImageView* iv = (UIImageView*)[cell.contentView viewWithTag: 1];
    // ...
    return cell;
}

Using our own cell subviews instead of the built-in cell style subviews has some clear advantages; we no longer have to perform an elaborate dance to escape from the restrictions imposed by the runtime. Still, the verbosity of this code is somewhat overwhelming. We can avoid this by designing the cell in a nib.

Designing a cell in a nib

In designing a cell in a nib, we start by creating a .xib file that will consist, in effect, solely of this one cell. In Xcode, create a new iOS User Interface View .xib file for iPhone. Let’s call it MyCell.xib. In the nib editor, delete the existing View and replace it with a Table View Cell from the Object library.

The cell’s design window shows a standard-sized cell; you can resize it as desired, but the actual size of the cell in the interface will be dictated by the table view’s width and its rowHeight (or the delegate’s response to tableView:heightForRowAtIndexPath:). The cell already has a contentView, and any subviews you add will be inside that; do not subvert that arrangement.

You can choose a built-in table view cell style in the Style pop-up menu of the Attributes inspector, and this gives you the default subviews, locked in their standard positions; for example, if you choose Basic, the textLabel appears, and if you specify an image, the imageView appears. If you set the Style pop-up menu to Custom, you start with a blank slate. Let’s do that.

We’ll implement, from scratch, the same subviews we’ve already implemented in the preceding two examples: a UILabel on the left side of the cell, and a UIImageView on the right side. Just as when adding subviews in code, we should set each subview’s autoresizing behavior or constraints, and give each subview a tag, so that later, in tableView:cellForRowAtIndexPath:, we’ll be able to refer to the label and the image view using viewWithTag:, exactly as in the previous example:

UILabel* lab = (UILabel*)[cell viewWithTag: 2];
UIImageView* iv = (UIImageView*)[cell viewWithTag: 1];
// ...
return cell;

The only remaining question is how to load the cell from the nib. This the Really Cool Part. When we register with the table view, which we’re currently doing in viewDidLoad, instead of calling registerClass:forCellReuseIdentifier:, we call registerNib:forCellReuseIdentifier:. To specify the nib, call UINib’s class method nibWithNibName:bundle:, like this:

[self.tableView registerNib:[UINib nibWithNibName:@"MyCell" bundle:nil]
     forCellReuseIdentifier:@"Cell"];

That’s all there is to it! In tableView:cellForRowAtIndexPath:, when we call dequeueReusableCellWithIdentifier:forIndexPath:, if the table has no free reusable cell already in existence, the nib will automatically be loaded and the cell will be instantiated from it and returned to us.

You may wonder how that’s possible, when we haven’t specified a File’s Owner class or added an outlet from the File’s Owner to the cell in the nib. The answer is that the nib conforms to a specific format. The UINib instance method instantiateWithOwner:options: can load a nib with a nil owner; regardless, it returns an NSArray of the nib’s instantiated top-level objects. A nib registered with the table view is expected to have exactly one top-level object, and that top-level object is expected to be a UITableViewCell; that being so, the cell can easily be extracted from the resulting NSArray, as it is the array’s only element. Our nib meets those expectations!

The advantages of this approach should be immediately obvious. The subviews can now be designed in the nib, and code that was creating and configuring each subview can be deleted. For example, suppose we previously had this code:

if (![cell viewWithTag:1]) {
    UIImageView* iv = [UIImageView new];
    iv.tag = 1;
    [cell.contentView addSubview:iv];
    UILabel* lab = [UILabel new];
    lab.tag = 2;
    [cell.contentView addSubview:lab];
    // ...
    lab.font = [UIFont fontWithName:@"Helvetica-Bold" size:16];
    lab.lineBreakMode = NSLineBreakByWordWrapping;
    lab.numberOfLines = 2;
}

All of that can now be eliminated, including setting the label’s font, lineBreakMode, and numberOfLines; those configurations are to be applied to the label in every cell, so they can be performed in the nib instead.

In tableView:cellForRowAtIndexPath:, we are still referring to the cell’s subviews by way of viewWithTag:. There’s nothing wrong with that, but perhaps you’d prefer to use names. Now that we’re designing the cell in a nib, that’s easy. Provide a UITableViewCell subclass with outlet properties, and configure the nib file accordingly:

The result is that in our implementation of tableView:cellForRowAtIndexPath:, once we’ve typed the cell as a MyCell (which will require importing "MyCell.h"), the compiler will let us use the property names to access the subviews:

MyCell* cell =
    [tableView dequeueReusableCellWithIdentifier:@"Cell"
        forIndexPath:indexPath];
UILabel* lab = cell.theLabel;
UIImageView* iv = cell.theImageView;

When your table view comes from a storyboard, it is open to you to employ any of the ways of obtaining and designing its cells that I’ve already described. There is also an additional option, available only if you’re using a UITableViewController subclass — that is, there needs to be a UITableViewController scene in the storyboard. In that case, you can have the table view obtain the cells from the storyboard itself, and you can also design the cell directly in the table view in the storyboard.

To experiment with this way of obtaining and designing a cell, start with an iPhone project based on the Single View Application template:

The table view now contains a single table view cell with a content view. You can do in this cell exactly what we were doing before when designing a table view cell in a .xib file.

So, let’s do that. I like being able to refer to my custom cell subviews with property names. Our procedure is just like what we did in the previous example:

There is one question I have not yet answered: How will your code tell the table view to get its cells from the storyboard? Clearly, not by calling registerClass:forCellReuseIdentifier:, and not by calling registerNib:forCellReuseIdentifier:; each of those would do something perfectly valid, but not the thing we want done in this case. The answer is that you don’t register anything with the table view at all! Instead, when you call dequeueReusableCellWithIdentifier:forIndexPath:, you supply an identifier that matches the prototype cell’s identifier in the storyboard.

So, return once more to the storyboard:

Now RootViewController’s tableView:cellForRowAtIndexPath: works exactly as it did in the previous example:

MyCell* cell =
    [tableView dequeueReusableCellWithIdentifier:@"Cell"
        forIndexPath:indexPath];
UILabel* lab = cell.theLabel;
UIImageView* iv = cell.theImageView;

If you call dequeueReusableCellWithIdentifier:forIndexPath: with an identifier that you have not registered with the table view and that doesn’t match the identifier of a prototype cell in the storyboard, your app will crash (with a helpful message in the console).

The structure and content of the actual data portrayed in a table view comes from the data source, an object pointed to by the table view’s dataSource property and adopting the UITableViewDataSource protocol. The data source is thus the heart and soul of the table. What surprises beginners is that the data source operates not by setting the table view’s structure and content, but by responding on demand. The data source, qua data source, consists of a set of methods that the table view will call when it needs information. This architecture has important consequences for how you write your code, which can be summarized by these simple guidelines:

This may sound daunting, but you’ll be fine as long as you maintain an unswerving adherence to the principles of model–view–controller. How and when you accumulate the actual data, and how that data is structured, is a model concern. Acting as a data source is a controller concern. So you can acquire and arrange your data whenever and however you like, just so long as when the table view actually turns to you and asks what to do, you can lay your hands on the relevant data rapidly and consistently. You’ll want to design the model in such a way that the controller can access any desired piece of data more or less instantly.

Another source of confusion for beginners is that methods are rather oddly distributed between the data source and the delegate, an object pointed to by the table view’s delegate property and adopting the UITableViewDelegate protocol; in some cases, one may seem to be doing the job of the other. This is not usually a cause of any real difficulty, because the object serving as data source will probably also be the object serving as delegate. Nevertheless, it is rather inconvenient when you’re consulting the documentation; you’ll probably want to keep the data source and delegate documentation pages open simultaneously as you work.

Note

If a table view’s contents are known beforehand, you can alternatively design the entire table, including the contents of individual cells, in a storyboard. I’ll give an example later in this chapter.

The Three Big Questions

Like Katherine Hepburn in Pat and Mike, the basis of your success (as a data source) is your ability, at any time, to answer the Three Big Questions. The questions the table view will ask you are a little different from the questions Mike asks Pat, but the principle is the same: know the answers, and be able to recite them at any moment. Here they are:

I have nothing particular to say about precisely how you’re going to fulfill these obligations. It all depends on your data model and what your table is trying to portray. The important thing is to remember that you’re going to be receiving an NSIndexPath specifying a section and a row, and you need to be able to lay your hands on the data corresponding to that slot now and configure the cell now. So construct your model, and your algorithm for consulting it in the Three Big Questions, and your way of configuring the cell, in accordance with that necessity.

For example, suppose our table is to list the names of the Pep Boys. Our data model might be an NSArray of string names (self.pep). Our table has only one section. So our code might look like this:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    if (!self.pep) // data not ready?
        return 0;
    return 1;
}
- (NSInteger)tableView:(UITableView *)tableView
        numberOfRowsInSection:(NSInteger)section {
    return [self.pep count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    MyCell *cell =
        [tableView dequeueReusableCellWithIdentifier:@"Cell"
            forIndexPath:indexPath];
    cell.theLabel.text = (self.pep)[indexPath.row];
    return cell;
}

At this point you may be feeling some exasperation. You want to object: “But that’s trivial!” Exactly so! Your access to the data model should be trivial. That’s the sign of a data model that’s well designed for access by your table view’s data source. Your implementation of tableView:cellForRowAtIndexPath: might have some interesting work to do in order to configure the form of the cell, but accessing the actual data should be simple and boring.

For example, consider Figure 6-1. The actual code that fetches the data is trivial:

FPItem* item = self.parsedData.items[indexPath.row];
NSString* title = item.title;
NSString* blurb = item.blurbOfItem;

That’s all there is to it. And the reason why that’s all there is to it is that I’ve structured the data model to be ready for access in exactly this way. (To be sure, there then follow about thirty lines of code elaborately — but very quickly — formatting the layout of the text within the cell.)

Reusing Cells

Another important goal of tableView:cellForRowAtIndexPath: should be to conserve resources by reusing cells. As I’ve already explained, once a cell’s row is no longer visible on the screen, that cell can be slotted into a row that is visible — with its portrayed data appropriately modified, of course! — so that only a few more than the number of simultaneously visible cells will ever need to be instantiated.

A table view is ready to implement this strategy for you; all you have to do is call dequeueReusableCellWithIdentifier:forIndexPath:. For any given identifier, you’ll be handed either a newly minted cell or a reused cell that previously appeared in the table view but is now no longer needed because it has scrolled out of view.

The table view can maintain more than one cache of reusable cells; this could be useful if your table view contains more than one type of cell (where the meaning of the concept “type of cell” is pretty much up to you). This is why you must name each cache, by attaching an identifier string to any cell that can be reused. All the examples in this chapter (and in this book, and in fact in every UITableView I’ve ever created) use just one cache and just one identifier, but there can be more than one. If you’re using a storyboard as a source of cells, there would then need to be more than one prototype cell.

To prove to yourself the efficiency of the cell-caching architecture, do something to differentiate newly instantiated cells from reused cells, and count the newly instantiated cells, like this:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}
- (NSInteger)tableView:(UITableView *)tableView
        numberOfRowsInSection:(NSInteger)section {
    return 1000;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
        cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    MyCell *cell =
        [tableView dequeueReusableCellWithIdentifier:@"Cell"
            forIndexPath:indexPath];
    UILabel* lab = cell.theLabel;
    lab.text = [NSString stringWithFormat:@"This is row %d of section %d",
                indexPath.row, indexPath.section];
    if (lab.tag != 999) {
        lab.tag = 999;
        NSLog(@"%@", @"New cell");
    }
    return cell;
}

When we run this code and scroll through the table, every cell is numbered correctly, so there appear to be 1000 cells. But the log messages show that only about a dozen distinct cells are ever actually created.

Be certain that your table view code passes that test, and that you are truly reusing cells! Fortunately, one of the benefits of calling dequeueReusableCellWithIdentifier:forIndexPath: is that it forces you to use a valid reuse identifier. Still, I’ve seen beginners obtain a cell in some other way, or even call dequeueReusableCellWithIdentifier:forIndexPath: without understanding what it really does, only to instantiate a fresh cell manually in the next line. Don’t do that.

When your tableView:cellForRowAtIndexPath: implementation configures individual cells (stage 5 in Example 8-1), the cell might be new or reused; at this point in your code, you don’t know or care which. Therefore, you should always configure everything about the cell that might need configuring. If you fail to do this, and if the cell is reused, you might be surprised when some aspect of the cell is left over from its previous use; similarly, if you fail to do this, and if the cell is new, you might be surprised when some aspect of the cell isn’t configured at all.

As usual, I learned that lesson the hard way. In the TidBITS News app, there is a little loudspeaker icon that should appear in a given cell in the master view’s table view only if there is a recording associated with this article. So I initially wrote this code:

if (item.enclosures && [item.enclosures count])
    cell.speaker.hidden = NO;

That turned out to be a mistake, because the cell might be reused, and therefore always had a visible loudspeaker icon if, in a previous usage, that cell had ever had a visible loudspeaker icon! The solution was to rewrite the logic to cover all possibilities completely, like this:

cell.speaker.hidden = !(item.enclosures && [item.enclosures count]);

You do get a sort of second bite of the cherry: there’s a delegate method, tableView:willDisplayCell:forRowAtIndexPath:, that is called for every cell just before it appears in the table. This is absolutely the last minute to configure a cell. But don’t misuse this method. You’re functioning as the delegate here, not the data source; you may set the final details of the cell’s appearance, but you shouldn’t be consulting the data model at this point.

An additional delegate method (introduced in iOS 6) is tableView:didEndDisplayingCell:forRowAtIndexPath:. This tells you that the cell no longer appears in the interface and has become free for reuse. You could take advantage of this to tear down any resource-heavy customization of the cell — I’ll give an example in Chapter 24 — or simply to prepare it somehow for subsequent future reuse.

Table View Sections

Your table data can be expressed as divided into sections. You might clump your data into sections for various reasons (and doubtless there are other reasons beyond these):

Don’t confuse the section headers and footers with the header and footer of the table as a whole. The latter are view properties of the table view itself and are set through its properties tableHeaderView and tableFooterView, discussed earlier in this chapter. The table header appears only when the table is scrolled all the way down; the table footer appears only when the table is scrolled all the way up.

A section header or footer, on the other hand, appears before or after its section, but (in a nongrouped table) it also detaches itself while the user scrolls the table, positioning itself at the top or bottom of the table view and floating over the scrolled rows, so that the user always knows what section is currently being viewed.

The number of sections is determined by your reply to numberOfSectionsInTableView:. For each section, the table view will consult your data source and delegate to learn whether this section has a header or a footer, or both, or neither (the default).

The UITableViewHeaderFooterView class (introduced in iOS 6) is a UIView subclass intended specifically for use as the view of a header or footer; much like a table view cell, it is reusable. It has the following properties:

You can supply a header or footer in two ways:

Header or footer title string
You implement the data source method tableView:titleForHeaderInSection: or tableView:titleForFooterInSection: (or both). Return nil to indicate that the given section has no header (or footer). The header or footer view itself is a UITableViewHeaderFooterView, and is reused automatically: there will be only as many as needed for simultaneous display on the screen. The string you supply becomes the view’s textLabel.text; however, in a grouped style table the string’s capitalization may be changed. To avoid that, use the second way of supplying the header or footer.
Header or footer view

You implement the delegate method tableView:viewForHeaderInSection: or tableView:viewForFooterInSection: (or both). The view you supply is used as the entire header or footer and is automatically resized to the table’s width and the section header or footer height (I’ll discuss how the height is determined in a moment).

You are not required to return a UITableViewHeaderFooterView, but you will probably want to, in order to take advantage of reusability. To do so, the procedure is much like making a cell reusable. You register beforehand with the table view by calling registerClass:forHeaderFooterViewReuseIdentifier:. To supply the reusable view, send the table view dequeueReusableHeaderFooterViewWithIdentifier:; the result will be either a newly instantiated view or a reused view.

You can then configure this view as desired. For example, you can set its textLabel.text, or you can give its contentView custom subviews. In the latter case, be sure to set proper autoresizing or constraints, so that the subviews will be positioned and sized appropriately when the view itself is resized.

In addition, two pairs of delegate methods permit you to perform final configurations on your header or footer views:

It is possible to implement both viewFor... and titleFor.... In that case, viewFor... is called first, and if it returns a UITableViewHeaderFooterView, titleFor... will set its textLabel.text. If you implement both methods and you want heightFor... to return the height as set by the table view based on titleFor..., return UITableViewAutomaticDimension.

Some lovely effects can be created by making use of the fact that a header or footer view will be further forward than the table’s cells. For example, a header with transparency shows the cells as they scroll behind it; a header with a shadow casts that shadow on the adjacent cell.

Now let’s talk about where the header or footer view’s data will come from. Clearly, a table that is to have section headers or footers (or both) may require some advance planning in the formation of its data model. Just as with a cell, a section title must be readily available so that it can be supplied quickly in real time. A structure that I commonly use is a pair of parallel arrays: an array of strings containing the section names, and an array of subarrays containing the data for each section.

For example, suppose we intend to display the names of all 50 U.S. states in alphabetical order as the rows of a table view, and that we wish to divide the table into sections according to the first letter of each state’s name. I’ll prepare the data model by walking through the alphabetized list of state names, creating a new section name and a new subarray when I encounter a new first letter:

NSString* s =
    [NSString stringWithContentsOfFile:
        [[NSBundle mainBundle] pathForResource:@"states" ofType:@"txt"]
            encoding:NSUTF8StringEncoding error:nil];
NSArray* states = [s componentsSeparatedByString:@"\n"];
self.sectionNames = [NSMutableArray array];
self.sectionData = [NSMutableArray array];
NSString* previous = @"";
for (NSString* aState in states) {
    // get the first letter
    NSString* c = [aState substringToIndex:1];
    // only add a letter to sectionNames when it's a different letter
    if (![c isEqualToString: previous]) {
        previous = c;
        [self.sectionNames addObject: [c uppercaseString]];
        // and in that case, also add a new subarray to our array of subarrays
        NSMutableArray* oneSection = [NSMutableArray array];
        [self.sectionData addObject: oneSection];
    }
    [[self.sectionData lastObject] addObject: aState];
}

The value of this preparatory dance is evident when we are bombarded with questions from the table view about cells and headers; supplying the answers is trivial:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [self.sectionNames count];
}
- (NSInteger)tableView:(UITableView *)tableView
        numberOfRowsInSection:(NSInteger)section {
    return [(self.sectionData)[section] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell =
        [tableView dequeueReusableCellWithIdentifier:@"Cell"
                                        forIndexPath:indexPath];
    NSString* s = self.sectionData[indexPath.section][indexPath.row];
    cell.textLabel.text = s;
    return cell;
}
- (NSString *)tableView:(UITableView *)tableView
        titleForHeaderInSection:(NSInteger)section {
    return self.sectionNames[section];
}

Let’s modify that example to illustrate customization of a header view. I’ve already registered my header identifier in viewDidLoad:

[self.tableView registerClass:[UITableViewHeaderFooterView class]
     forHeaderFooterViewReuseIdentifier:@"Header"];

Now, instead of tableView:titleForHeaderInSection:, I’ll implement tableView:viewForHeaderInSection:. For completely new views, I’ll place my own label and an image view inside the contentView and give them their basic configuration; then I’ll perform individual configuration on all views, new or reused (very much like tableView:cellForRowAtIndexPath:):

- (UIView *)tableView:(UITableView *)tableView
        viewForHeaderInSection:(NSInteger)section {
    UITableViewHeaderFooterView* h =
        [tableView dequeueReusableHeaderFooterViewWithIdentifier:@"Header"];
    if (![h.tintColor isEqual: [UIColor redColor]]) {
        h.tintColor = [UIColor redColor];
        h.backgroundView = [UIView new];
        h.backgroundView.backgroundColor = [UIColor blackColor];
        UILabel* lab = [UILabel new];
        lab.tag = 1;
        lab.font = [UIFont fontWithName:@"Georgia-Bold" size:22];
        lab.textColor = [UIColor greenColor];
        lab.backgroundColor = [UIColor clearColor];
        [h.contentView addSubview:lab];
        UIImageView* v = [UIImageView new];
        v.tag = 2;
        v.backgroundColor = [UIColor blackColor];
        v.image = [UIImage imageNamed:@"us_flag_small.gif"];
        [h.contentView addSubview:v];
        lab.translatesAutoresizingMaskIntoConstraints = NO;
        v.translatesAutoresizingMaskIntoConstraints = NO;
        [h.contentView addConstraints:
         [NSLayoutConstraint
          constraintsWithVisualFormat:@"H:|-5-[lab(25)]-10-[v(40)]"
          options:0 metrics:nil views:@{@"v":v, @"lab":lab}]];
        [h.contentView addConstraints:
         [NSLayoutConstraint
          constraintsWithVisualFormat:@"V:|[v]|"
           options:0 metrics:nil views:@{@"v":v}]];
        [h.contentView addConstraints:
         [NSLayoutConstraint
          constraintsWithVisualFormat:@"V:|[lab]|"
           options:0 metrics:nil views:@{@"lab":lab}]];
    }
    UILabel* lab = (UILabel*)[h.contentView viewWithTag:1];
    lab.text = self.sectionNames[section];
    return h;
}

If your table view has the plain style, you can add an index down the right side of the table, where the user can tap or drag to jump to the start of a section — helpful for navigating long tables. To generate the index, implement the data source method sectionIndexTitlesForTableView:, returning an NSArray of string titles to appear as entries in the index. This works even if there are no section headers. The index will appear only if the number of rows exceeds the table view’s sectionIndexMinimumDisplayRowCount property value; the default is 0, so the index is always displayed by default. You will want the index entries to be short — preferably just one character — because they will be partially obscuring the right edge of the table; plus, each cell’s content view will shrink to compensate, so you’re sacrificing some cell real estate.

For our list of state names, that’s trivial, as it should be:

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    return self.sectionNames;
}

You can modify three properties that affect the index’s appearance:

Normally, there will be a one-to-one correspondence between the index entries and the sections; when the user taps an index entry, the table jumps to the start of the corresponding section. However, under certain circumstances you may want to customize this correspondence.

For example, suppose there are 100 sections, but there isn’t room to display 100 index entries comfortably on the iPhone. The index will automatically curtail itself, omitting some index entries and inserting bullets to suggest the omission, but you might prefer to take charge of the situation.

To do so, supply a shorter index, and implement the data source method tableView:sectionForSectionIndexTitle:atIndex:, returning the number of the section to jump to. You are told both the title and the index number of the section index listing that the user chose, so you can use whichever is convenient.

Apple’s documentation elaborates heavily on the details of implementing the model behind a table with an index and suggests that you rely on a class called UILocalizedIndexedCollation. This class is effectively a way of generating an ordered list of letters of the alphabet, with methods for helping to sort an array of strings and separate it into sections. This might be useful if you need your app to be localized, because the notion of the alphabet and its order changes automatically depending on the user’s preferred language.

Unfortunately, you can’t readily use a UILocalizedIndexCollation to implement your own sort order. For example, UILocalizedIndexCollation was of no use to me in writing my Greek and Latin vocabulary apps, in which the Greek words must be sorted, sectioned, and indexed according to the Greek alphabet, and the Latin words use a reduced version of the English alphabet (no initial J, K, or V through Z). Thus I’ve never actually bothered to use UILocalizedIndexedCollation.

The table view has no direct connection to the underlying data. If you want the table view display to change because the underlying data have changed, you have to cause the table view to refresh itself; basically, you’re requesting that the Big Questions be asked all over again. At first blush, this seems inefficient (“regenerate all the data??”); but it isn’t. Remember, in a table that caches reusable cells, there are no cells of interest other than those actually showing in the table at this moment. Thus, having worked out the layout of the table through the section header and footer heights and row heights, the table has to regenerate only those cells that are actually visible.

You can cause the table data to be refreshed using any of several methods:

The second two methods can perform animations that cue the user as to what’s changing. For the rowAnimation: argument, you’ll pass one of the following:

If all you need to do is to refresh the index, call reloadSectionIndexTitles; this calls the data source’s sectionIndexTitlesForTableView:.

If you want the table view to be laid out freshly without reloading any cells, send it beginUpdates immediately followed by endUpdates. The section and row structure of the table will be asked for, along with calculation of all heights, but no cells and no headers or footers are requested. This is useful as a way of alerting the table that its measurements have changed. It might be considered a misuse of an updates block (the real use of such a block is discussed later in this chapter); but Apple takes advantage of this trick in the Table View Animations and Gestures example, in which a pinch gesture is used to change a table’s row height in real time, so it must be legal.

It is also possible to access and alter a table’s individual cells directly. This can be a lightweight approach to refreshing the table, plus you can supply your own animation within the cell as it alters its appearance. It is important to bear in mind, however, that the cells are not the data (view is not model). If you change the content of a cell manually, make sure that you have also changed the model corresponding to it, so that the row will appear correctly if its data is reloaded later.

To do this, you need direct access to the cell you want to change. You’ll probably want to make sure the cell is visible within the table view’s bounds; nonvisible cells don’t really exist (except as potential cells waiting in the reuse cache), and there’s no point changing them manually, as they’ll be changed when they are scrolled into view, through the usual call to tableView:cellForRowAtIndexPath:.

Here are some UITableView methods that mediate between cells, rows, and visibility:

By the same token, you can get access to the views constituting headers and footers, by calling headerViewForSection: or footerViewForSection:. Thus you could modify a view directly. You should assume that if a section is returned by indexPathsForVisibleRows, its header or footer might be visible.

If you want to grant the user some interface for requesting that a table view be refreshed, you might like to use a UIRefreshControl. You aren’t required to use this; it’s just Apple’s attempt to provide a standard interface. In iOS 7, it is located behind the top of the scrolling part of the table view, behind the table view’s backgroundView. To request a refresh, the user scrolls the table view downward to reveal the refresh control and holds long enough to indicate that this scrolling is deliberate. The refresh control then acknowledges visually that it is refreshing, and remains visible until refreshing is complete.

To give a table view a refresh control, assign a UIRefreshControl to the table view controller’s refreshControl property; it is a control (UIControl, Chapter 12), and you will want to hook its Value Changed event to an action method:

self.refreshControl = [UIRefreshControl new];
[self.refreshControl addTarget:self action:@selector(doRefresh:)
    forControlEvents:UIControlEventValueChanged];

You can also configure a table view controller’s refresh control in the nib editor.

Once a refresh control’s action message has fired, the control remains visible and indicates by animation (similar to an activity indicator) that it is refreshing, until you send it the endRefreshing message:

-(void)doRefresh:(UIRefreshControl*) sender {
    // ... refresh the table data ...
    [sender endRefreshing];
}

You can initiate a refresh animation in code with beginRefreshing, but this does not fire the action message or display the refresh control; to display it, scroll the table view:

[self.tableView setContentOffset:
    CGPointMake(0,-self.refreshControl.bounds.size.height)
        animated:YES];
[self.refreshControl beginRefreshing];
// ... now actually do refresh, and later send endRefreshing

A refresh control also has these properties:

refreshing (read-only)
Whether the refresh control is refreshing.
tintColor
The refresh control’s color. It is not inherited from the view hierarchy (I regard this as a bug).
attributedTitle
Styled text displayed below the refresh control’s activity indicator. On attributed strings, see Chapter 10.
backgroundColor (inherited from UIView)
If you give a table view controller’s refreshControl a background color, that color completely covers the table view’s own background color when the refresh control is revealed. For some reason, I find the drawing of the attributedTitle more reliable if the refresh control has a background color.

Variable Row Heights

Most tables have rows that are all the same height, as set by the table view’s rowHeight. However, the delegate’s tableView:heightForRowAtIndexPath: can be used to make different rows different heights. You can see an example in the TidBITS News app (Figure 6-1).

Implementing variable row heights can be a tricky business. One problem is that as a cell is reused or instantiated from a nib, its height may be changed, which is likely to expose any weaknesses in your practice for laying out subviews. (Can you guess that I’m speaking from experience here?) A mistake in the autoresizingMask value of subviews can result in display errors that would not have arisen if all the rows were the same height. You may have to resort to manual layout (such as implementing layoutSubviews in a UITableViewCell subclass); alternatively, constraints can be a big help.

Another problem has to do with time consumed and the order of events. The runtime needs to know the heights of everything in your table immediately, before it starts asking for any cells. In effect, this means you have to gather all the data and lay out all the cells before you can start showing the data in any single row. If that takes a long time, your table view will remain blank during the calculation. Plus, there is now a danger of duplicating your own work, since you’ll be laying out every cell twice, once when you’re asked for all the heights and again when you’re asked for the actual cell.

In iOS 7, this second problem is not as severe as it used to be, thanks to three new table view properties:

To accompany those, there are also three new table view delegate methods:

The idea is that if you implement these, the runtime will use their values whenever it gathers the heights for the whole table at once. Then, when a cell or a header or footer is about to appear onscreen, the runtime will ask for its real height in the usual way. You can thus get the table to appear quickly and postpone the calculation of an actual height until it is needed (by which time you might have calculated all the real heights in a background thread).

To illustrate, I’ll describe the strategy I use in the TidBITS News app. It predates the estimated heights, so I don’t use them. The key is an NSMutableArray property, self.rowHeights, consisting of NSNumbers wrapping floats. (An array is all that’s needed, because the table has just one section; the row number can thus serve directly as an index into the array.) Once that array is constructed, it can be used to supply a requested height instantly.

As I’ve already said, calculating a cell height requires me to lay out that cell. Each cell displays an attributed string. I have a utility method, attributedStringForIndexPath:, which generates the attributed string for a given row of the table (by consulting the data model to obtain the title and description of the story represented by that row).

When the delegate’s tableView:heightForRowAtIndexPath: is called, either we’ve already constructed self.rowHeights or we haven’t. If we haven’t, we construct it, by calling attributedStringForIndexPath: for every index path, laying out its cell rectangle, and storing the height of that rectangle. Now we have all the heights, so from now on we simply return the one we are asked for:

-(CGFloat)tableView:(UITableView *)tableView
        heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (!self.rowHeights) {
        NSMutableArray* heights = [NSMutableArray array];
        for (int i = 0; i < self.parsedData.items.count; i++) {
            NSAttributedString* s =
                [self attributedStringForIndexPath:
                    [NSIndexPath indexPathForRow:i inSection:0]];
            CGFloat maxheight = // maximum permissible cell height
            CGRect r =
                [s boundingRectWithSize:CGSizeMake(320,maxheight)
                    options:
                        NSStringDrawingUsesLineFragmentOrigin |
                        NSStringDrawingTruncatesLastVisibleLine
                        context:nil];
            [heights addObject:@(r.size.height)];
        }
        self.rowHeights = heights;
    }
    CGFloat result = [self.rowHeights[indexPath.row] floatValue];
    return result;
}

In tableView:cellForRowAtIndexPath:, I call attributedStringForIndexPath: again. (I suppose I could have cached the attributed strings in an array as well, but this is not a time-consuming routine.) The wonderful thing is that, thanks to dequeueReusableCellWithIdentifier:forIndexPath:, the cell already has the correct height, and constraints have taken care of positioning its subviews correctly. So I just plop the attributed string into it, configure the rest of the cell, and hand back the cell:

- (UITableViewCell *)tableView:(UITableView *)tableView
        cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    FPItem* item = self.parsedData.items[indexPath.row];
    Cell* cell =
        [tableView dequeueReusableCellWithIdentifier:@"Cell"
            forIndexPath:indexPath];
    cell.drawer.attributedText = [self attributedStringForIndexPath: indexPath];
    cell.speaker.hidden = !(item.enclosures && [item.enclosures count]);
    return cell;
}

Now I’ll adapt my approach to use estimated heights. My table view has an estimatedRowHeight of 80; on average, this should set the scrolling content height to be roughly correct from the outset. All I really need to change now is my implementation of tableView:heightForRowAtIndexPath:, to take account of the fact that I can no longer predict when this method will be called for any given row. If we don’t have any self.rowHeights array at all, I create it, and I fill it with [NSNull null] as a placeholder. If the self.rowHeights entry for the requested row is [NSNull null], I calculate the actual row height and set it as that entry. Finally, I return the self.rowHeights entry for the requested row:

-(CGFloat)tableView:(UITableView *)tableView
        heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (!self.rowHeights) {
        NSMutableArray* heights = [NSMutableArray array];
        for (int i = 0; i < self.parsedData.items.count; i++)
            heights[i] = [NSNull null];
        self.rowHeights = heights;
    }
    int ix = indexPath.row;
    if (self.rowHeights[indexPath.row] == [NSNull null]) {
        NSAttributedString* s =
            [self attributedStringForIndexPath:
                [NSIndexPath indexPathForRow:ix inSection:0]];
        CGFloat maxheight = // maximum permissible cell height
        CGRect r =
            [s boundingRectWithSize:CGSizeMake(320,maxheight)
                options:
                    NSStringDrawingUsesLineFragmentOrigin |
                    NSStringDrawingTruncatesLastVisibleLine
                    context:nil];
        self.rowHeights[ix] = @(r.size.height);
    }
    CGFloat result = [self.rowHeights[ix] floatValue];
    return result;
}

This method is called four or five times at launch, for the cells that will initially appear onscreen, and after that only for individual cells as they are scrolled into view.

A table view cell has a normal state, a highlighted state (according to its highlighted property), and a selected state (according to its selected property). It is possible to change these states directly, possibly with animation, by calling setHighlighted:animated: or setSelected:animated: on the cell. But you don’t want to act behind the table’s back, so you are more likely to manage selection through the table view, letting the table view manage and track the state of its cells.

These two states are closely related. In particular, when a cell is selected, it propagates the highlighted state down through its subviews by setting each subview’s highlighted property if it has one. That is why a UILabel’s highlightedTextColor applies when the cell is selected. Similarly, a UIImageView (such as the cell’s imageView) can have a highlightedImage that is shown when the cell is selected, and a UIControl (such as a UIButton) takes on its highlighted state when the cell is selected.

One of the chief purposes of your table view is likely to be to let the user select a cell. This will be possible, provided you have not set the value of the table view’s allowsSelection property to NO. The user taps a normal cell, and the cell switches to its selected state. By default, this will mean that the cell is redrawn with a gray background view, but you can change this at the individual cell level, as I’ve already explained: you can set a cell’s selectedBackgroundView (or multipleSelectionBackgroundView), or change its selectionStyle. If the user taps an already selected cell, by default it stays selected.

Table views can permit the user to select multiple cells simultaneously. Set the table view’s allowsMultipleSelection property to YES. If the user taps an already selected cell, by default it is deselected.

Your code can learn and manage the selection through these UITableView instance methods:

To deselect all currently selected rows, call selectRowAtIndexPath:animated:scrollPosition: with a nil index path. Reloading a cell’s data also deselects that cell, and calling reloadData deselects all selected rows.

Response to user selection is through the table view’s delegate:

Despite their names, the two “will” methods are actually “should” methods and expect a return value:

The “highlight” methods are more sensibly named, and they arrive first, so you can return NO from tableView:shouldHighlightRowAtIndexPath: to prevent a cell from being selected.

Let’s focus in more detail on the relationship between a cell’s highlighted state and its selected state. They are, in fact, two different states. When the user touches a cell, the cell passes through a complete highlight cycle. Then, if the touch turns out to be the beginning of a scroll motion, the cell is unhighlighted immediately, and the cell is not selected. Otherwise, the cell is unhighlighted and selected.

But the user doesn’t know the difference between these two states: whether the cell is highlighted or selected, the cell’s subviews are highlighted, and the selectedBackgroundView appears. Thus, if the user touches and scrolls, what the user sees is the flash of the selectedBackgroundView and the highlighted subviews, until the table begins to scroll and the cell returns to normal. If the user touches and lifts the finger, the selectedBackgroundView and highlighted subviews appear and remain; there is actually a moment in the sequence where the cell has been highlighted and then unhighlighted and not yet selected, but the user doesn’t see any momentary unhighlighting of the cell, because no redraw moment occurs (see Chapter 4).

Here’s a summary of the sequence:

  1. The user’s finger goes down. If shouldHighlight permits, the cell highlights, which propagates to its subviews. Then didHighlight arrives.
  2. There is a redraw moment. Thus, the user will see the cell as highlighted (including the appearance of the selectedBackgroundView), regardless of what happens next.
  3. The user either starts scrolling or lifts the finger. The cell unhighlights, which also propagates to its subviews, and didUnhighlight arrives.

    1. If the user starts scrolling, there is a redraw moment, so the user now sees the cell unhighlighted. The sequence ends.
    2. If the user merely lifts the finger, there is no redraw moment, so the cell keeps its highlighted appearance. The sequence continues.
  4. If willSelect permits, the cell is selected, and didSelect arrives. The cell is not highlighted, but highlighting is propagated to its subviews.
  5. There’s another redraw moment. The user still sees the cell as highlighted (including the appearance of the selectedBackgroundView).

When tableView:willSelectRowAtIndexPath: is called because the user taps a cell, and if this table view permits only single cell selection, tableView:willDeselectRowAtIndexPath: will be called subsequently for any previously selected cells.

Here’s an example of implementing tableView:willSelectRowAtIndexPath:. The default behavior for allowsSelection (not multiple selection) is that the user can select by tapping, and the cell remains selected; if the user taps a selected row, the selection does not change. We can alter this so that tapping a selected row deselects it:

- (NSIndexPath*) tableView:(UITableView*)tv
        willSelectRowAtIndexPath:(NSIndexPath*)ip {
    if ([tv cellForRowAtIndexPath:ip].selected) {
        [tv deselectRowAtIndexPath:ip animated:NO];
        return nil;
    }
    return ip;
}

Navigation From a Table View

An extremely common response to user selection is navigation. A master–detail architecture is typical: the table view lists things the user can see in more detail, and a tap displays the detailed view of the selected thing. On the iPhone, very often the table view will be in a navigation interface, and you will respond to user selection by creating the detail view and pushing it onto the navigation controller’s stack.

For example, here’s the code from my Albumen app that navigates from the list of albums to the list of songs in the album that the user has tapped:

- (void) tableView:(UITableView *)tableView
        didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    TracksViewController *t =
        [[TracksViewController alloc]
            initWithMediaItemCollection:(self.albums)[indexPath.row]];
    [self.navigationController pushViewController:t animated:YES];
}

This interface is so common that Xcode’s Master–Detail Application project template implements it for you.

In a storyboard, when you draw a segue from a UITableViewCell, you are given a choice of two segue triggers: Selection Segue and Accessory Action. If you create a Selection Segue, the segue will be triggered when the user selects a cell. Thus you can readily push or present another view controller in response to cell selection.

If you’re using a UITableViewController, then by default, whenever the table view appears, the selection is cleared automatically in viewWillAppear:, and the scroll indicators are flashed in viewDidAppear:. You can prevent this automatic clearing of the selection by setting the table view controller’s clearsSelectionOnViewWillAppear to NO. I sometimes do that, preferring to implement deselection in viewDidAppear:; the effect is that when the user returns to the table, the row is still momentarily selected before it deselects itself:

- (void) viewDidAppear:(BOOL)animated {
    // deselect selected row
    [tableView selectRowAtIndexPath:nil animated:NO
        scrollPosition:UITableViewScrollPositionNone];
    [super viewDidAppear:animated];
}

By convention, if selecting a table view cell causes navigation, the cell should be given an accessoryType of UITableViewCellAccessoryDisclosureIndicator. This is a plain gray right-pointing chevron at the right end of the cell. The chevron itself doesn’t respond to user interaction; it’s not a button, but is just a visual cue that the user can tap the cell to learn more.

Two additional accessoryType settings are buttons:

To respond to the tapping of an accessory button, implement the table view delegate’s tableView:accessoryButtonTappedForRowWithIndexPath:. Or, in a storyboard, you can Control-drag a connection from a cell and choose the Accessory Action segue.

A common convention is that selecting the cell as a whole does one thing and tapping the detail button does something else. For example, in Apple’s Phone app, tapping a contact’s listing in the Recents table places a call to that contact, but tapping the detail button navigates to that contact’s detail view.

Another use of cell selection is to implement a choice among cells, where a section of a table effectively functions as an iOS alternative to OS X radio buttons. The table view usually has the grouped format. An accessoryType of UITableViewCellAccessoryCheckmark is typically used to indicate the current choice. Implementing radio button behavior is up to you.

As an example, I’ll implement the interface shown in Figure 8-2. The table view has the grouped style, with two sections. The first section, with a “Size” header, has three mutually exclusive choices: “Easy,” “Normal,” and “Hard.” The second section, with a “Style” header, has two choices: “Animals” or “Snacks.”

This is a static table; its contents are known beforehand and won’t change. In a case like this, if we’re using a UITableViewController subclass instantiated from a storyboard, the nib editor lets us design the entire table, including the headers and the cells and their content, directly in the storyboard. Select the table and set its Content pop-up menu in the Attributes inspector to Static Cells to make the table editable in this way (Figure 8-6).

Even though each cell is designed initially in the storyboard, I can still implement tableView:cellForRowAtIndexPath: to call super and then add further functionality. That’s how I’ll add the checkmarks. The user defaults are storing the current choice in each of the two categories; there’s a @"Size" preference and a @"Style" preference, each consisting of a string denoting the title of the chosen cell:

- (UITableViewCell *)tableView:(UITableView *)tv
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell* cell =
        [super tableView:tv cellForRowAtIndexPath:indexPath];
    NSUserDefaults* ud = [NSUserDefaults standardUserDefaults];
    cell.accessoryType = UITableViewCellAccessoryNone;
    if ([[ud valueForKey:@"Style"] isEqualToString:cell.textLabel.text] ||
            [[ud valueForKey:@"Size"] isEqualToString:cell.textLabel.text])
        cell.accessoryType = UITableViewCellAccessoryCheckmark;
    return cell;
}

When the user taps a cell, the cell is selected. I want the user to see that selection momentarily, as feedback, but then I want to deselect, adjusting the checkmarks so that that cell is the only one checked in its section. In tableView:didSelectRowAtIndexPath:, I set the user defaults, and then I reload the table view’s data. This removes the selection and causes tableView:cellForRowAtIndexPath: to be called to adjust the checkmarks:

- (void)tableView:(UITableView *)tv
        didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    NSUserDefaults* ud = [NSUserDefaults standardUserDefaults];
    NSString* setting = [tv cellForRowAtIndexPath:indexPath].textLabel.text;
    NSString* header =
        [self tableView:tv titleForHeaderInSection:indexPath.section];
    [ud setValue:setting forKey:header];
    [tv reloadData]; // deselect all cells, reassign checkmark as needed
}

A UITableView is a UIScrollView, so everything you already know about scroll views is applicable (Chapter 7). In addition, a table view supplies two convenience scrolling methods:

  • scrollToRowAtIndexPath:atScrollPosition:animated:
  • scrollToNearestSelectedRowAtScrollPosition:animated:

The scrollPosition parameter is as for selectRowAtIndexPath:..., discussed earlier in this chapter.

The following UITableView methods mediate between the table’s bounds coordinates on the one hand and table structure on the other:

  • indexPathForRowAtPoint:
  • indexPathsForRowsInRect:
  • rectForSection:
  • rectForRowAtIndexPath:
  • rectForFooterInSection:
  • rectForHeaderInSection:

The table’s own header and footer are direct subviews of the table view, so their positions within the table’s bounds are given by their frames.

Table View State Restoration

If a UITableView participates in state saving and restoration (Chapter 6), the restoration mechanism would like to restore the selection and the scroll position. In iOS 7, this behavior is automatic; the restoration mechanism knows both what cells should be visible and what cells should be selected, in terms of their index paths. If that’s satisfactory, you’ve no further work to do.

In some apps, however, there is a possibility that when the app is relaunched, the underlying data may have been rearranged somehow. Perhaps what’s meaningful in dictating what the user should see in such a case is not the previous index paths but the previous data. The state saving and restoration mechanism doesn’t know anything about the relationship between the cells and the underlying data. If you’d like to tell it, adopt the UIDataSourceModelAssociation protocol and implement two methods:

Devising a system of unique identification and incorporating it into your data model is up to you. In the TidBITS News app, for example, where I was using this mechanism in iOS 6, it happens that my bits of data come from a parsed RSS feed and have a guid property that is a global unique identifier. So implementing the first method is easy:

- (NSString *) modelIdentifierForElementAtIndexPath:(NSIndexPath *)idx
                                             inView:(UIView *)view {
    FPItem* item = self.parsedData.items[idx.row];
    return item.guid;
}

Implementing the second method is a little more work; I walk the data model looking for the object whose guid matches the identifier in question, and construct its index path:

- (NSIndexPath*) indexPathForElementWithModelIdentifier:(NSString *)identifier
                                                 inView:(UIView *)view {
    __block NSIndexPath* path = nil;
    [self.parsedData.items
        enumerateObjectsUsingBlock:^(FPItem* item, NSUInteger idx, BOOL *stop)
    {
        if ([item.guid isEqualToString:identifier]) {
            path = [NSIndexPath indexPathForRow:idx inSection:0];
            *stop = YES;
        }
    }];
    return path;
}

It is crucial, when the app is relaunched, that the table should have data before that method is called, so I also call reloadData in my implementation of decodeRestorableStateWithCoder:.

In iOS 7, however, I no longer do any of that, since the automatic index path–based restoration of the table view’s scroll position is sufficient.

A table view is a common way to present the results of a search performed through a search field (a UISearchBar; see Chapter 12). This is such a standard interface, in fact, that a class is provided, UISearchDisplayController, to mediate between the search field where the user enters a search term and the table view listing the results of the search.

When the user initially taps in the search field, the UISearchDisplayController automatically constructs a new interface along with a nice animation. This indicates to the user that the search field is ready to receive input; when the user proceeds to enter characters into the search field, the UISearchDisplayController will display its own search results table view in this interface. The UISearchBar has a Cancel button that the user can tap to dismiss the interface created by the UISearchDisplayController.

The UISearchDisplayController needs the following things:

A UISearchDisplayController’s searchContentsController needn’t be a UITableViewController, and the data that the user is searching needn’t be the content of an existing table view. But they frequently are! That’s because the mental connection between a table and a search is a natural one; when the search results are presented as a table view, the user feels that the search field is effectively filtering the contents of the original table view. A single object may thus be playing all of the following roles:

A common point of confusion among beginners, when using this architecture, is to suppose that the search bar is genuinely filtering the original table. It isn’t! The search bar and the UISearchDisplayController know nothing of your table. What’s being searched is just some data — whatever data you care to search. The fact that this may be the model data for your table is purely secondary.

Moreover, there are two distinct tables: yours (the original table view) and the UISearchDisplayController’s (the search results table view). You own the former, just as you would if no search were involved; you probably have a view controller that manages it, very likely a UITableViewController whose tableView is this table. But the search results table is a completely different table; you do not have a view controller managing it (the UISearchDisplayController does), and in particular it is not your UITableViewController’s tableView. However, if you wish, you can make it look as if these are the same table, by configuring the two tables and their cells the same way — typically, with the same code.

To illustrate, we will implement a table view that is searchable through a UISearchBar and that displays the results of that search in a second table view managed by a UISearchDisplayController.

The first question is how to make the search field appear along with the table view.

One approach is to make the search field be the table view’s header view. Indeed, this is such a common arrangement that, in the nib editor, if you drag a Search Bar and Search Display Controller object onto a UITableView, the search field automatically becomes the table’s header view and a UISearchDisplayController is created for you with all properties hooked up appropriately through outlets, much as I just described.

It is more educational, however, to create the UISearchDisplayController and the UISearchBar in code. So let’s do that. I’ll adapt my earlier example displaying the names of states in a section-based table, so that it becomes searchable.

We already have a table managed by a UITableViewController. In viewDidLoad, we create the search bar and slot it in as the table’s header view; we then load the data and scroll the header view out of sight. We also create the UISearchDisplayController and tie it to the search bar — and to ourselves (the UITableViewController) as the UISearchDisplayController’s controller, delegate, search table data source, and search table delegate, as well as making ourselves the UISearchBar delegate. We also retain the UISearchDisplayController by assigning it to a property, so that it doesn’t vanish in a puff of smoke before we can use it:

UISearchBar* b = [UISearchBar new];
[b sizeToFit];
b.autocapitalizationType = UITextAutocapitalizationTypeNone;
b.delegate = self;
[self.tableView setTableHeaderView:b];
[self.tableView reloadData];
[self.tableView
    scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]
    atScrollPosition:UITableViewScrollPositionTop animated:NO];
UISearchDisplayController* c =
    [[UISearchDisplayController alloc] initWithSearchBar:b
                                      contentsController:self];
self.sdc = c; // retain the UISearchDisplayController
c.delegate = self;
c.searchResultsDataSource = self;
c.searchResultsDelegate = self;

Populating the search results table in response to what the user does in the UISearchBar is up to us. The amazing thing is that this is almost working already, because we have been fiendishly clever at the outset: the UITableViewController is both data source and delegate for the original table view, as well as data source and delegate for the search results table view, so by default the search results table will portray the same data as the original table!

It isn’t quite working, however. We need to make a few tweaks.

As the UISearchDisplayController’s table view comes into existence, we get a delegate message. This is the place to perform any initial configurations on the UISearchDisplayController’s table view. For example, if my viewDidLoad is setting my table view’s separator style to UITableViewCellSeparatorStyleNone, and if I want the two tables to look identical, this would be the place to set the UISearchDisplayController’s table view’s separator style to UITableViewCellSeparatorStyleNone as well. In this example, I’ll take advantage of this moment to suppress display of headers in the search results table:

-(void)searchDisplayController:(UISearchDisplayController *)controller
        didLoadSearchResultsTableView:(UITableView *)tableView {
    // configure search results table to look like our table
    tableView.sectionHeaderHeight = 0;
}

Next, let’s talk about where the search results table is going to get its cells. At present, the original table gets its cells like this:

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell =
        [tableView dequeueReusableCellWithIdentifier:@"Cell"
            forIndexPath:indexPath];

But when the search results table calls into this same delegate method, tableView will be the search results table. That’s wrong. We don’t want to use cells from the search results table; we want to use cells from the original table, so that they’ll be structured like those of the original table. So we change tableView to self.tableView, referring explicitly to our original table as the source of cells:

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell =
        [self.tableView dequeueReusableCellWithIdentifier:@"Cell"
            forIndexPath:indexPath];

Our search interface has now sprung to life! We can type in the search field, and a search results table appears. It looks just like our original table, except that it has no sections. The only problem is that the search bar itself is not doing anything. Instead of searching, we’re displaying all the same data as the original table.

Clearly, we need to check whether the table view that’s talking to us is the search results table view (this will be the UISearchDisplayController’s searchResultsTableView). If it is, we want to limit our returned data with respect to the search bar’s text. The strategy for doing this should be fairly obvious if we are maintaining our source data in a sensible model.

Recall that our original table is displaying the names of the 50 United States, which it is getting from a mutable array of mutable arrays of strings called sectionData. Let’s restructure our responses to the Three Big Questions so that they refer to this array as model, like this:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [self.sectionNames count];
}
- (NSInteger)tableView:(UITableView *)tableView
        numberOfRowsInSection:(NSInteger)section {
    NSArray* model = self.sectionData;
    return [model[section] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell =
        [self.tableView dequeueReusableCellWithIdentifier:@"Cell"
            forIndexPath:indexPath];
    NSArray* model = self.sectionData;
    NSString* s = model[indexPath.section][indexPath.row];
    cell.textLabel.text = s;
    return cell;
}

To implement searching, each time we speak of the NSArray called model, we will decide whether it should be self.sectionData, as now, or whether it should be a different array that is filtered with respect to the current search — let’s call it self.filteredSectionData. There are two occurrences of this line:

NSArray* model = self.sectionData;

They are now to be replaced by this:

NSArray* model =
    (tableView == self.sdc.searchResultsTableView) ?
        self.filteredSectionData : self.sectionData;

The remaining question is when and how this self.filteredSectionData array should be calculated. An excellent approach, given our small and readily available data set, is to generate a new set of search results every time the user types in the search field, effectively implementing a “live” search (Figure 8-7). We are informed of the user typing through a UISearchBar delegate method, searchBar:textDidChange:, so we implement this to generate self.filteredSectionData freshly. There is no need to reload the search results table’s data, as by default the UISearchDisplayController will do that automatically:

- (void)searchBar:(UISearchBar *)searchBar
        textDidChange:(NSString *)searchText {
    // this is the search criteria
    NSPredicate* p =
        [NSPredicate predicateWithBlock: ^BOOL(id obj, NSDictionary *d) {
            NSString* s = obj;
            return ([s rangeOfString:searchText
                options:NSCaseInsensitiveSearch].location != NSNotFound);
        }];
    // generate filtered section data
    NSMutableArray* filteredData = [NSMutableArray new];
    // sectionData is an array of arrays; for every array...
    for (NSMutableArray* arr in self.sectionData) {
        // generate an array of strings passing the search criteria
        [filteredData addObject: [arr filteredArrayUsingPredicate:p]];
    }
    self.filteredSectionData = filteredData;
}

Our search interface is now working! In the rest of this section, I’ll discuss some additional optional tweaks.

A UISearchBar can display scope buttons, letting the user alter the meaning of the search. If you add these, then of course you must take them into account when filtering the model data. For example, let’s have two scope buttons, “Starts With” and “Contains”:

UISearchBar* b = [UISearchBar new];
[b sizeToFit];
b.scopeButtonTitles = @[@"Starts With", @"Contains"];

Our filtering routine must now take the state of the scope buttons into account. Moreover, the search results table view will reload when the user changes the scope; we can detect this in another UISearchBar delegate method, searchBar:selectedScopeButtonIndexDidChange:. If we’re doing a live search, we must respond by filtering the data then as well. To prevent repetition, we’ll abstract the filtering routine into a method of its own:

- (void) filterData: (UISearchBar*) sb {
    NSPredicate* p =
        [NSPredicate predicateWithBlock: ^BOOL(id obj, NSDictionary *d) {
            NSString* s = obj;
            NSStringCompareOptions options = NSCaseInsensitiveSearch;
            if (sb.selectedScopeButtonIndex == 0)
                options |= NSAnchoredSearch;
            return ([s rangeOfString:sb.text
                options:options].location != NSNotFound);
        }];
    NSMutableArray* filteredData = [NSMutableArray new];
    for (NSMutableArray* arr in self.sectionData) {
        [filteredData addObject: [arr filteredArrayUsingPredicate:p]];
    }
    self.filteredSectionData = filteredData;
}
- (void)searchBar:(UISearchBar *)searchBar
        textDidChange:(NSString *)searchText {
    [self filterData: searchBar];
}
- (void)searchBar:(UISearchBar *)searchBar
        selectedScopeButtonIndexDidChange:(NSInteger)selectedScope {
    [self filterData: searchBar];
}

In our original table, the search bar is initially scrolled out of sight. Let’s make it easier for the user to discover its existence and summon it. In an indexed list — one with sections and an index running down the right side — a “magnifying glass” search symbol can be made to appear in the index by including UITableViewIndexSearch (usually as the first item) in the string array returned from sectionIndexTitlesForTableView:. The section names are already in an array called sectionNames:

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    if (tableView != self.tableView)
        return nil;
    return [@[UITableViewIndexSearch]
        arrayByAddingObjectsFromArray:self.sectionNames];
}

The magnifying glass appears in the section index, but now there’s a bug: the correspondence between index entries and sections is off by one. To fix that, we need to implement tableView:sectionForSectionIndexTitle:atIndex:. If the user taps on or above the magnifying glass in the index, we scroll to reveal the search field (and we’ll also have to return a bogus section number, but there is no penalty for that):

- (NSInteger)tableView:(UITableView *)tableView
        sectionForSectionIndexTitle:(NSString *)title
                            atIndex:(NSInteger)index {
    if (index == 0)
        [tableView scrollRectToVisible:tableView.tableHeaderView.frame
                              animated:NO];
    return index-1;
}

Whenever the search results table becomes empty (because the search bar is nonempty and self.filteredSectionData is nil), the words “No Results” appear in a label superimposed on it. I find this label incredibly obnoxious; yet, after all these years, Apple still hasn’t granted programmers an official way to remove or customize it. Here’s an unofficial way:

-(BOOL)searchDisplayController:(UISearchDisplayController *)controller
        shouldReloadTableForSearchString:(NSString *)searchString {
    dispatch_async(dispatch_get_main_queue(), ^{
        for (UIView* v in controller.searchResultsTableView.subviews) {
            if ([v isKindOfClass: [UILabel class]] &&
                    [[(UILabel*)v text] isEqualToString:@"No Results"]) {
                [(UILabel*)v setText: @""];
                break;
            }
        }
    });
    return YES;
}

New in iOS 7, if our view controller (the UISearchDisplayController’s searchContentsController) is the child of a UINavigationController, we can make the search bar appear in the navigation bar. To do so, set the UISearchDisplayController’s displaysSearchBarInNavigationBar property to YES. If the UISearchDisplayController also has a navigationItem, it is used instead of our view controller’s navigationItem to populate the navigation bar.

This doesn’t mean that you can toggle displaysSearchBarInNavigationBar to make a search bar appear and disappear in your navigation bar. On the contrary, the UISearchDisplayController must be created early, and the search bar then will always be present in the navigation bar for this view controller. However, you’re in a navigation interface, so you can easily work around this limitation by navigating to and from the searchable view controller.

Also, a search bar in a navigation bar can’t have scope buttons, and its Cancel button doesn’t come and go automatically — either it is constantly present (because you set its showsCancelButton to YES) or it never appears.

This example code is from our view controller’s viewDidLoad:

UISearchBar* b = [UISearchBar new];
b.delegate = self;
b.showsCancelButton = YES;
b.autocapitalizationType = UITextAutocapitalizationTypeNone;
UISearchDisplayController* c =
    [[UISearchDisplayController alloc] initWithSearchBar:b
        contentsController:self];
self.sdc = c; // retain the UISearchDisplayController
c.delegate = self;
c.searchResultsDataSource = self;
c.searchResultsDelegate = self;
c.displaysSearchBarInNavigationBar = YES;

The result is that our navigation bar’s title view is the search bar. The back bar button item, if any, is shown, so the user can pop this view controller in the usual way. Any other buttons you want to have appear in the navigation bar must added to the UISearchDisplayController’s navigationItem (not the view controller’s navigationItem, which is ignored).

A UISearchBar has many properties through which its appearance can be configured; I’ll discuss them in Chapter 12. Both the UISearchBar and UISearchDisplayController send their delegate numerous messages that you can take advantage of to customize behavior; consult the documentation. A UISearchBar in a UIToolbar on the iPad can display its results in a popover; I’ll talk about that in Chapter 9.

Table View Editing

A table view cell has a normal state and an editing state, according to its editing property. The editing state is typically indicated visually by one or more of the following:

As with selection, you could set a cell’s editing property directly (or use setEditing:animated: to get animation), but you are more likely to let the table view manage editability. Table view editability is controlled through the table view’s editing property, usually by sending the table the setEditing:animated: message. The table is then responsible for putting its cells into edit mode.

A cell in edit mode can also be selected by the user if the table view’s allowsSelectionDuringEditing or allowsMultipleSelectionDuringEditing is YES. But this would be unusual.

Putting the table into edit mode is usually left up to the user. A typical interface would be an Edit button that the user can tap. In a navigation interface, we might have our view controller supply the button as the navigation item’s right button:

UIBarButtonItem* bbi =
    [[UIBarButtonItem alloc]
        initWithBarButtonSystemItem:UIBarButtonSystemItemEdit
        target:self action:@selector(doEdit:)];
self.navigationItem.rightBarButtonItem = bbi;

Our action handler will be responsible for putting the table into edit mode, so in its simplest form it might look like this:

- (void) doEdit: (id) sender {
    [self.tableView setEditing:YES animated:YES];
}

But that does not solve the problem of getting out of editing mode. The standard solution is to have the Edit button replace itself by a Done button:

- (void) doEdit: (id) sender {
    int which;
    if (![self.tableView isEditing]) {
        [self.tableView setEditing:YES animated:YES];
        which = UIBarButtonSystemItemDone;
    } else {
        [self.tableView setEditing:NO animated:YES];
        which = UIBarButtonSystemItemEdit;
    }
    UIBarButtonItem* bbi = [[UIBarButtonItem alloc]
        initWithBarButtonSystemItem:which
        target:self action:@selector(doEdit:)];
    self.navigationItem.rightBarButtonItem = bbi;
}

However, it turns out that all of that is completely unnecessary! If we want standard behavior, it’s already implemented for us. A UIViewController supplies an editButtonItem that calls the UIViewController’s setEditing:animated: when tapped, tracks whether we’re in edit mode with the UIViewController’s editing property, and changes its own title accordingly (Edit or Done). Moreover, a UITableViewController’s implementation of setEditing:animated: is to call setEditing:animated: on its table view. Thus, if we’re using a UITableViewController, we get all of that behavior for free, just by inserting the editButtonItem into our interface:

self.navigationItem.rightBarButtonItem = self.editButtonItem;

When the table view enters edit mode, it consults its data source and delegate about the editability of individual rows:

If the user taps an insert button (the Plus button) or a delete button (the Delete button that appears after the user taps the Minus button), the data source is sent the tableView:commitEditingStyle:forRowAtIndexPath: message and is responsible for obeying it. In your response, you will probably want to alter the structure of the table, and UITableView methods for doing this are provided:

The row animations here are effectively the same ones discussed earlier in connection with refreshing table data; “left” for an insertion means to slide in from the left, and for a deletion it means to slide out to the left, and so on. The two “move” methods provide animation with no provision for customizing it.

If you’re issuing more than one of these commands, you can combine them by surrounding them with beginUpdates and endUpdates, forming an updates block. An updates block combines not just the animations but the requested changes themselves. This relieves you from having to worry about how a command is affected by earlier commands in the same updates block; indeed, order of commands within an updates block doesn’t really matter.

For example, if you delete row 1 of a certain section and then (in a separate command) delete row 2 of the same section, you delete two successive rows, just as you would expect; the notion “2” does not change its meaning because you deleted an earlier row first, because you didn’t delete an earlier row first — the updates block combines the commands for you, interpreting both index paths with respect to the state of the table before any changes are made. If you perform insertions and deletions together in one animation, the deletions are performed first, regardless of the order of your commands, and the insertion row and section numbers refer to the state of the table after the deletions.

An updates block can also include reloadRows... and reloadSections... commands (but not reloadData).

I need hardly emphasize once again (but I will anyway) that view is not model. It is one thing to rearrange the appearance of the table, another to alter the underlying data. It is up to you to make certain you do both together. Do not, even for a moment, permit the data and the view to get out of synch with each other. If you delete a row, remove from the model the datum that it represents. The runtime will try to help you with error messages if you forget to do this, but in the end the responsibility is yours. I’ll give examples as we proceed.

Deletion of table items is the default, so there’s not much for us to do in order to implement it. If our view controller is a UITableViewController and we’ve displayed the Edit button as its navigation item’s right button, everything happens automatically: the user taps the Edit button, the view controller’s setEditing:animated: is called, the table view’s setEditing:animated: is called, and the cells all show the Minus button at the left end. The user can then tap a Minus button; a Delete button is shown at the cell’s right end. You can customize the Delete button’s title with the table delegate method tableView:titleForDeleteConfirmationButtonForRowAtIndexPath:.

What is not automatic is the actual response to the Delete button. For that, we need to implement tableView:commitEditingStyle:forRowAtIndexPath:. Typically, you’ll remove the corresponding entry from the underlying model data, and you’ll call deleteRowsAtIndexPaths:withRowAnimation: or deleteSections:withRowAnimation: to update the appearance of the table. As I said a moment ago, you must delete the row or section in such a way as to keep the table display coordinated with the model’s structure. Otherwise, the app may crash (with an extremely helpful error message).

To illustrate, let’s suppose once again that the underlying model is a pair of parallel arrays, a mutable array of strings (sectionNames) and a mutable array of arrays (sectionData). Our approach will be in two stages:

- (void)tableView:(UITableView *)tableView
        commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
         forRowAtIndexPath:(NSIndexPath *)ip {
    [self.sectionData[ip.section] removeObjectAtIndex:ip.row];
    if ([self.sectionData[ip.section] count] == 0) {
        [self.sectionData removeObjectAtIndex: ip.section];
        [self.sectionNames removeObjectAtIndex: ip.section];
        [tableView deleteSections:[NSIndexSet indexSetWithIndex: ip.section]
                 withRowAnimation:UITableViewRowAnimationAutomatic];
        [tableView reloadSectionIndexTitles];
    } else {
        [tableView deleteRowsAtIndexPaths:@[ip]
                         withRowAnimation:UITableViewRowAnimationAutomatic];
    }
}

The user can also delete a row by sliding it to the left to show its Delete button without having explicitly entered edit mode; no other row is editable, and no other editing controls are shown. (In iOS 7, the table cell is itself inside a little horizontal scroll view; the user is actually scrolling the cell to the left, revealing the Delete button behind it.) This feature is implemented “for free” by virtue of our having supplied an implementation of tableView:commitEditingStyle:forRowAtIndexPath:. If you’re like me, your first response will be: “Thanks for the free functionality, Apple, and now how do I turn this off?” Because the Edit button is already using the UIViewController’s editing property to track edit mode, we can take advantage of this and refuse to let any cells be edited unless the view controller is in edit mode:

- (UITableViewCellEditingStyle)tableView:(UITableView *)aTableView
        editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
    return self.editing ?
        UITableViewCellEditingStyleDelete : UITableViewCellEditingStyleNone;
}

A table item might have content that the user can edit directly, such as a UITextField (Chapter 10). Because the user is working in the view, you need a way to reflect the user’s changes into the model. This will probably involve putting yourself in contact with the interface objects where the user does the editing.

To illustrate, I’ll implement a table view cell with a text field that is editable when the cell is in editing mode. Imagine an app that maintains a list of names and phone numbers. A name and phone number are displayed as a grouped style table, and they become editable when the user taps the Edit button (Figure 8-8).

We don’t need a button at the left end of the cell when it’s being edited:

- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView
        editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
    return UITableViewCellEditingStyleNone;
}

A UITextField is editable if its enabled is YES. To tie this to the cell’s editing state, it is probably simplest to implement a custom UITableViewCell class. I’ll call it MyCell, and I’ll design it in the nib, giving it a single UITextField that’s pointed to through a property called textField. In the code for MyCell, we override didTransitionToState:, as follows:

- (void) didTransitionToState:(UITableViewCellStateMask)state {
    [super didTransitionToState:state];
    if (state == UITableViewCellStateEditingMask) {
        self.textField.enabled = YES;
    }
    if (state == UITableViewCellStateDefaultMask) {
        self.textField.enabled = NO;
    }
}

In the table’s data source, we make ourselves the text field’s delegate when we create and configure the cell:

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {
     MyCell* cell =
         [tableView dequeueReusableCellWithIdentifier:@"Cell"
            forIndexPath:indexPath];
     if (indexPath.section == 0)
         cell.textField.text = self.name;
     if (indexPath.section == 1) {
         cell.textField.text = self.numbers[indexPath.row];
         cell.textField.keyboardType = UIKeyboardTypeNumbersAndPunctuation;
     }
     cell.textField.delegate = self;
     return cell;
}

We are the UITextField’s delegate, so we are responsible for implementing the Return button in the keyboard to dismiss the keyboard (I’ll talk more about this in Chapter 10):

- (BOOL)textFieldShouldReturn:(UITextField *)tf {
    [tf endEditing:YES];
    return NO;
}

When a text field stops editing, we are its delegate, so we can hear about it in textFieldDidEndEditing:. We work out which cell it belongs to, and update the model accordingly:

- (void)textFieldDidEndEditing:(UITextField *)tf {
    // some cell's text field has finished editing; which cell?
    UIView* v = tf;
    do {
        v = v.superview;
    } while (![v isKindOfClass: [UITableViewCell class]]);
    MyCell* cell = (MyCell*)v;
    // update data model to match
    NSIndexPath* ip = [self.tableView indexPathForCell:cell];
    if (ip.section == 1)
        self.numbers[ip.row] = cell.textField.text;
    else if (ip.section == 0)
        self.name = cell.textField.text;
}

Inserting Table Items

You are unlikely to attach a Plus (insert) button to every row. A more likely interface is that when a table is edited, every row has a Minus button except the last row, which has a Plus button; this shows the user that a new row can be appended at the end of the list.

Let’s implement this for phone numbers in our name-and-phone-number app, allowing the user to give a person any quantity of phone numbers (Figure 8-9):

- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView
           editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.section == 1) {
        NSInteger ct =
            [self tableView:tableView numberOfRowsInSection:indexPath.section];
        if (ct-1 == indexPath.row)
            return UITableViewCellEditingStyleInsert;
        return UITableViewCellEditingStyleDelete;
    }
    return UITableViewCellEditingStyleNone;
}

The person’s name has no editing control (a person must have exactly one name), so we prevent it from indenting in edit mode:

- (BOOL)tableView:(UITableView *)tableView
        shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.section == 1)
        return YES;
    return NO;
}

When the user taps an editing control, we must respond. We immediately force our text fields to cease editing: the user have may tapped the editing control while editing, and we want our model to contain the very latest changes, so this is effectively a way of causing our textFieldDidEndEditing: to be called. The model for our phone numbers is a mutable array of strings (self.numbers). We already know what to do when the tapped control is a delete button; things are similar when it’s an insert button, but we’ve a little more work to do. The new row will be empty, and it will be at the end of the table; so we append an empty string to the self.numbers model array, and then we insert a corresponding row at the end of the view. But now two successive rows have a Plus button; the way to fix that is to reload the first of those rows. Finally, we also show the keyboard for the new, empty phone number, so that the user can start editing it immediately; we do that outside the updates block:

- (void) tableView:(UITableView *)tableView
        commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
        forRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView endEditing:YES];
    if (editingStyle == UITableViewCellEditingStyleInsert) {
        [self.numbers addObject: @""];
        NSInteger ct = [self.numbers count];
        [tableView beginUpdates];
        [tableView insertRowsAtIndexPaths:
         @[[NSIndexPath indexPathForRow: ct-1 inSection:1]]
                       withRowAnimation:UITableViewRowAnimationAutomatic];
        [self.tableView reloadRowsAtIndexPaths:
            @[[NSIndexPath indexPathForRow:ct-2 inSection:1]]
                          withRowAnimation:UITableViewRowAnimationAutomatic];
        [tableView endUpdates];
        // crucial that this next bit be *outside* the updates block
        UITableViewCell* cell =
            [self.tableView cellForRowAtIndexPath:
                     [NSIndexPath indexPathForRow:ct-1 inSection:1]];
        [((MyCell*)cell).textField becomeFirstResponder];
    }
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        [self.numbers removeObjectAtIndex:indexPath.row];
        [tableView beginUpdates];
        [tableView deleteRowsAtIndexPaths:@[indexPath]
                         withRowAnimation:UITableViewRowAnimationAutomatic];
        [tableView reloadSections:[NSIndexSet indexSetWithIndex:1]
                 withRowAnimation:UITableViewRowAnimationAutomatic];
        [tableView endUpdates];
    }
}

Rearranging Table Items

If the data source implements tableView:moveRowAtIndexPath:toIndexPath:, the table displays a reordering control at the right end of each row in editing mode (Figure 8-9), and the user can drag it to rearrange table items. The reordering control can be suppressed for individual table items by implementing tableView:canMoveRowAtIndexPath:. The user is free to move rows that display a reordering control, but the delegate can limit where a row can be moved to by implementing tableView:targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath:.

To illustrate, we’ll add to our name-and-phone-number app the ability to rearrange phone numbers. There must be multiple phone numbers to rearrange:

- (BOOL)tableView:(UITableView *)tableView
        canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.section == 1 && [self.numbers count] > 1)
        return YES;
    return NO;
}

A phone number must not be moved out of its section, so we implement the delegate method to prevent this. We also take this opportunity to dismiss the keyboard if it is showing.

- (NSIndexPath *)tableView:(UITableView *)tableView
        targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath*)sourceIndexPath
        toProposedIndexPath:(NSIndexPath*)proposedDestinationIndexPath {
    [tableView endEditing:YES];
    if (proposedDestinationIndexPath.section == 0)
        return [NSIndexPath indexPathForRow:0 inSection:1];
    return proposedDestinationIndexPath;
}

After the user moves an item, tableView:moveRowAtIndexPath:toIndexPath: is called, and we trivially update the model to match. We also reload the table, to fix the editing controls:

- (void)tableView:(UITableView *)tableView
        moveRowAtIndexPath:(NSIndexPath *)fromIndexPath
               toIndexPath:(NSIndexPath *)toIndexPath {
    NSString* s = self.numbers[fromIndexPath.row];
    [self.numbers removeObjectAtIndex: fromIndexPath.row];
    [self.numbers insertObject:s atIndex: toIndexPath.row];
    [tableView reloadData];
}

Dynamic Table Items

A table may be rearranged not just in response to the user working in edit mode, but for some other reason entirely. In this way, many interesting and original interfaces are possible.

In this example, we permit the user to double tap on a section header as a way of collapsing or expanding the section — that is, we’ll suppress or permit the display of the rows of the section, with a nice animation as the change takes place. (This idea is shamelessly stolen from a WWDC 2010 video.)

One more time, our data model consists of the two arrays, sectionNames and sectionData. I’ve also got an NSMutableSet, hiddenSections, in which I’ll list the sections that aren’t displaying their rows. That list is all I’ll need, since either a section is showing all its rows or it’s showing none of them:

- (NSInteger)tableView:(UITableView *)tableView
         numberOfRowsInSection:(NSInteger)section {
     if ([self.hiddenSections containsObject:@(section)])
         return 0;
     return [self.sectionData[section] count];
}

We need a correspondence between a section header and the number of its section. It’s odd that UITableView doesn’t give us such a correspondence; it provides indexPathForCell:, but there is no sectionForHeaderFooterView:. My solution is to subclass UITableViewHeaderFooterView and give my subclass a public property section, to which I assign the current section number whenever tableView:viewForHeaderInSection: is called:

- (UIView *)tableView:(UITableView *)tableView
        viewForHeaderInSection:(NSInteger)section {
    MyHeaderView* h =
        [tableView dequeueReusableHeaderFooterViewWithIdentifier:@"Header"];
    // ...
    h.section = section;
    return h;
}

The section headers are a UITableViewHeaderFooterView subclass with userInteractionEnabled set to YES and a UITapGestureRecognizer attached, so we can detect a double tap. When the user double taps a section header, we learn from the header what section this is; we also find out from the model how many rows this section has, as we’ll need to know that later, regardless of whether we’re about to show or hide rows. Then we look for the section number in our hiddenSections set. If it’s there, we’re about to display the rows, so we remove that section number from hiddenSections; now we work out the index paths of the rows we’re about to insert, and we insert them. If it’s not there, we’re about to hide the rows, so we insert that section number into hiddenSections; again, we work out the index paths of the rows we’re about to delete, and we delete them:

- (void) tap: (UIGestureRecognizer*) g {
    UITableViewHeaderFooterView* v = (id)g.view;
    NSUInteger sec = v.section;
    NSUInteger ct = [(NSArray*)(self.sectionData)[sec] count];
    NSNumber* secnum = @(sec);
    if ([self.hiddenSections containsObject:secnum]) {
        [self.hiddenSections removeObject:secnum];
        [self.tableView beginUpdates];
        NSMutableArray* arr = [NSMutableArray array];
        for (int ix = 0; ix < ct; ix ++) {
            NSIndexPath* ip = [NSIndexPath indexPathForRow:ix inSection:sec];
            [arr addObject: ip];
        }
        [self.tableView insertRowsAtIndexPaths:arr
            withRowAnimation:UITableViewRowAnimationAutomatic];
        [self.tableView endUpdates];
        [self.tableView scrollToRowAtIndexPath:[arr lastObject]
                              atScrollPosition:UITableViewScrollPositionNone
                                      animated:YES];
    } else {
        [self.hiddenSections addObject:secnum];
        [self.tableView beginUpdates];
        NSMutableArray* arr = [NSMutableArray array];
        for (int ix = 0; ix < ct; ix ++) {
            NSIndexPath* ip = [NSIndexPath indexPathForRow:ix inSection:sec];
            [arr addObject: ip];
        }
        [self.tableView deleteRowsAtIndexPaths:arr
            withRowAnimation:UITableViewRowAnimationAutomatic];
        [self.tableView endUpdates];
    }
}

A menu, in iOS, is a sort of balloon containing tappable words such as Copy, Cut, and Paste. You can permit the user to display a menu from a table view cell by performing a long press on the cell. The long press followed by display of the menu gives the cell a selected appearance, but in iOS 7, the selected appearance goes away when the menu is dismissed.

To allow the user to display a menu from a table view’s cells, you implement three delegate methods:

Here’s an example where the user can summon a Copy menu from any cell (Figure 8-10):

- (BOOL)tableView:(UITableView *)tableView
        shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath {
    return YES;
}
- (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action
        forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
    return (action == NSSelectorFromString(@"copy:"));
}
- (void)tableView:(UITableView *)tableView performAction:(SEL)action
        forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
    NSString* s = self.sectionData[indexPath.section][indexPath.row];
    if (action == NSSelectorFromString(@"copy:")) {
        // ... do whatever copying consists of ...
    }
}

To add a custom menu item to the menu (other than copy:, cut:, and paste:) is a little more work. First, you must tell the shared UIMenuController to append the menu item to the global menu; the tableView:shouldShowMenuForRowAtIndexPath: delegate method is a good place to do this:

- (BOOL)tableView:(UITableView *)tableView
        shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath {
    // extra menu item
    UIMenuItem* mi =
        [[UIMenuItem alloc] initWithTitle:@"Abbrev"
         action:NSSelectorFromString(@"abbrev:")];
    [[UIMenuController sharedMenuController] setMenuItems:@[mi]];
    return YES;
}

We have now given the menu an additional menu item whose title is Abbrev, and whose action when the menu item is tapped is abbrev:. (I am imagining here a table of the names of U.S. states, where one can copy a state’s two-letter abbreviation to the clipboard.) If we want this menu item to appear in the menu, and if we want to respond to it when the user taps it, we must add that selector to the two performAction: delegate methods:

- (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action
        forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
    return (action == NSSelectorFromString(@"copy:") ||
            action == NSSelectorFromString(@"abbrev:"));
}
- (void)tableView:(UITableView *)tableView performAction:(SEL)action
        forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
    NSString* s = self.sectionData[indexPath.section][indexPath.row];
    if (action == NSSelectorFromString(@"copy:")) {
        // ... do whatever copying consists of ...
        NSLog(@"copying %@", s);
    }
    if (action == NSSelectorFromString(@"abbrev:")) {
        // ... do whatever abbreviating consists of ...
        NSLog(@"abbreviating %@", s);
    }
}

Now comes the tricky part: we must implement our custom selector, abbrev:, in the cell. We will therefore need our table to use a custom UITableViewCell subclass. Let’s call it MyCell:

#import "MyCell.h"
@implementation MyCell
-(void)abbrev:(id)sender {
    // ...
}
@end

The Abbrev menu item now appears when the user long-presses a cell of our table, and the cell’s abbrev: method is called when the user taps that menu item. We could respond directly to the tap in the cell, but it seems more consistent that our table view delegate should respond. So we work out what table view this cell belongs to and send its delegate the very message it is already expecting:

-(void)abbrev:(id)sender {
    // find my table view
    UIView* v = self;
    do {
        v = v.superview;
    } while (![v isKindOfClass:[UITableView class]]);
    UITableView* tv = (UITableView*) v;
    // ask it what index path we are
    NSIndexPath* ip = [tv indexPathForCell:self];
    // talk to its delegate
    if (tv.delegate && [tv.delegate respondsToSelector:
            @selector(tableView:performAction:forRowAtIndexPath:withSender:)])
        [tv.delegate tableView:tv performAction:_cmd
            forRowAtIndexPath:ip withSender:sender];
}

A collection view (UICollectionView) is a UIScrollView subclass that generalizes the notion of a UITableView. It has many similarities to a table view; indeed, knowing about table views, you know a great deal about collection views already:

The big difference between a table view and a collection view is how the collection view lays out its elements (cells and supplementary views). A table view lays out its cells in just one way: a vertically scrolling column, where the cells are the width of the table view, the height dictated by the table view or the delegate, and touching one another. A collection view doesn’t do that. In fact, a collection view doesn’t lay out its elements at all! That job is left to another class, a subclass of UICollectionViewLayout.

A UICollectionViewLayout subclass instance is responsible for the overall layout of the collection view that owns it. It does this by answering some Big Questions of its own, posed by the collection view; the most important are these:

collectionViewContentSize
How big is the entire layout? The collection view needs to know this, because the collection view is a scroll view (Chapter 7), and this will be the content size of the scrollable material that it will display.
layoutAttributesForElementsInRect:
Where do all the elements go? The layout attributes, as I’ll explain in more detail in a moment, are bundles of positional information.

To answer these questions, the collection view layout needs to ask the collection view some questions of its own, such as numberOfSections and numberOfItemsInSection:. (The collection view, in turn, gets the answers to those questions from its data source.)

The collection view layout can thus assign the elements any positions it likes, and the collection view will faithfully draw them in those positions within its content rectangle. The elements are actually of three kinds:

Cells
These, as I’ve already said, are parallel to table view cells. The collection view learns the position of each cell from its layout, and obtains the actual UICollectionViewCell objects from its data source by calling collectionView:cellForItemAtIndexPath:.
Supplementary views
These represent the generalized notion of a header or footer. It is up to the collection view layout to say what kinds of supplementary views it supports; it identifies each kind by a string. For example, if it wants each section to be able to have a header and a footer, it might call the kinds @"Header" and @"Footer". The collection view learns the position of each supplementary view from its layout, and obtains the actual UICollectionReusableView objects from its data source by calling collectionView:viewForSupplementaryElementOfKind:atIndexPath:.
Decoration views
These are not directly analogous to anything in a table view (they are closest, perhaps, to the section index). They don’t represent data; the collection view won’t ask the data source about them, and the collection view has no methods about them. They are purely up to the collection view layout; it defines any decoration view types, gives its decoration views actual view content, and states the positions its decoration views are to have. I’ve never written or used a collection view layout that implemented decoration views, and I’m not going to say any more about them.

The collection view layout can position its elements wherever it likes. That seems very open-ended, and indeed it is. To get you started, there’s a built-in UICollectionViewLayout subclass — UICollectionViewFlowLayout.

UICollectionViewFlowLayout arranges its cells in something like a grid. The grid can be scrolled either horizontally or vertically, so this grid is a series of rows or columns. Through properties and a delegate protocol of its own (UICollectionViewDelegateFlowLayout), the UICollectionViewFlowLayout instance lets you provide hints about how big the cells are and how they should be spaced out. It defines two supplementary view types, using them to let you give each section a header and a footer.

Figure 8-11 shows a collection view, laid out with a flow layout, from my Latin flashcard app. This interface simply lists the chapters and lessons into which the flashcards themselves are divided, and allows the user to jump to a desired lesson by tapping it. Previously, I was using a table view to present this list; when collection views were introduced (in iOS 6), I adopted one for this interface, and you can see why. Instead of a lesson item like “1a” occupying an entire row that stretches the whole width of a table, it’s just a little rectangle; in landscape orientation, the flow layout fits five of these rectangles onto a line for me. So a collection view is a much more compact and appropriate way to present this interface than a table view.

If UICollectionViewFlowLayout doesn’t quite meet your needs, you can subclass it, or you can subclass UICollectionViewLayout itself. I’ll talk more about that later on.

Here are the main classes associated with UICollectionView. This is just a conceptual overview; I don’t recite all the properties and methods of each class, which you can learn from the documentation:

UICollectionViewController

A UIViewController subclass. Like a table view controller, UICollectionViewController is convenient if a UICollectionView is to be a view controller’s view, but is not required. It is the delegate and data source of its collectionView by default. The initializer, if you create one in code, requires you to supply a layout instance for the collection view’s designated initializer:

RootViewController* rvc =
    [[RootViewController alloc]
        initWithCollectionViewLayout:[UICollectionViewFlowLayout new]];

Alternatively, there is a Collection View Controller nib object.

UICollectionView

A UIScrollView subclass. It has a backgroundColor (because it’s a view) and optionally a backgroundView in front of that. Its designated initializer requires you to supply a layout instance, which will be its collectionViewLayout. Alternatively, there is a Collection View nib object, which comes with a Collection View Flow Layout by default; you can change the collection view layout class with the Layout pop-up menu in the Collection View’s Attributes inspector.

A collection view’s methods are very much parallel to those of a UITableView, only fewer and simpler:

Having made those mental adjustments, you can guess correctly all the methods of a UICollectionView, except for a couple whose names begin with layoutAttributes.... To understand what they do, you need to know about UICollectionViewLayoutAttributes.

UICollectionViewLayoutAttributes
A UICollectionViewLayoutAttributes object is basically just a glorified struct, tying together an element’s index path within the collection view (indexPath) and the specifications for how and where it should be drawn — specifications that are remarkably reminiscent of view or layer properties, with names like frame, center, size, transform, and so forth. Layout attributes objects function as the mediators between the layout and the collection view; they are what the layout passes to the collection view to tell it where all the elements of the view should go.
UICollectionViewCell

An extremely minimal view class. It has a highlighted property and a selected property. It has a contentView, a selectedBackgroundView, a backgroundView, and of course (since it’s a view) a backgroundColor, layered in that order, just like a table view cell; everything else is up to you.

If you start with a collection view controller in a storyboard, you get prototype cells, just like a table view controller, which you obtain by dequeuing. Otherwise, you obtain cells through registration and dequeuing. This is all exactly parallel to UITableView.

UICollectionReusableView
The superclass of UICollectionViewCell — so it is even more minimal! This is the class of supplementary views such as headers and footers. You obtain reusable views through registration and dequeuing; if you’re using a flow layout in a storyboard, you are given a header and footer prototype view.
UICollectionViewLayout

The layout workhorse class for a collection view. A collection view cannot exist without a layout instance! As I’ve already said, the layout knows how much room all the subviews occupy, and supplies the collectionViewContentSize that sets the contentSize of the collection view, qua scroll view. In addition, the layout must answer questions from the collection view, by supplying a UICollectionViewLayoutAttributes object, or an NSArray of such objects, saying where and how elements should be drawn. These questions come in two categories:

The collection view also notifies the layout of pending changes through some methods whose names start with prepare and finalize. This is another way for the layout to participate in animations, or to perform other kinds of preparation and cleanup.

UICollectionViewLayout is an abstract class; to use it, you must subclass it, or start with the built-in subclass, UICollectionViewFlowLayout.

UICollectionViewFlowLayout

A concrete subclass of UICollectionViewLayout; you can use it as is, or you can subclass it. It lays out items in a grid that can be scrolled either horizontally or vertically, and it defines two supplementary element types to serve as the header and footer of a section. A collection view in the nib editor has a Layout pop-up menu that lets you choose a Flow layout, and you can configure the flow layout in the Size inspector; in a storyboard, you can even add and design a header and a footer.

A flow layout has a scroll direction, a sectionInset (the margins for a section), an itemSize along with a minimumInteritemSpacing and minimumLineSpacing, and a headerReferenceSize and footerReferenceSize. That’s all! At a minimum, if you want to see any section headers, you must assign the flow layout a headerReferenceSize, because the default is {0,0}. Otherwise, you get initial defaults that will at least allow you to see something immediately, such as an itemSize of {50,50} and reasonable default spacing between items and lines.

UICollectionViewFlowLayout also defines a delegate protocol, UICollectionViewDelegateFlowLayout. The flow layout automatically treats the collection view’s delegate as its own delegate. The section margins, item size, item spacing, line spacing, and header and footer size can be set individually through this delegate.

To show that using a collection view is easy, here’s how the view shown in Figure 8-11 is created. I have a UICollectionViewController subclass, LessonListController. Every collection view must have a layout, so LessonListController’s designated initializer initializes itself with a UICollectionViewFlowLayout:

- (id) initWithTerms: (NSArray*) data {
    UICollectionViewFlowLayout* layout = [UICollectionViewFlowLayout new];
    self = [super initWithCollectionViewLayout:layout];
    if (self) {
        // ... perform other self-initializations here ...
    }
    return self;
}

In viewDidLoad, we give the flow layout its hints about the sizes of the margins, cells, and headers, as well as registering for cell and header reusability:

- (void)viewDidLoad {
    [super viewDidLoad];
    UICollectionViewFlowLayout* layout =
        (id)self.collectionView.collectionViewLayout;
    layout.sectionInset = UIEdgeInsetsMake(10, 20, 10, 20);
    layout.headerReferenceSize = CGSizeMake(0,40); // only height matters
    layout.itemSize = CGSizeMake(70,45);
    [self.collectionView
        registerNib:[UINib nibWithNibName:@"LessonCell" bundle:nil]
        forCellWithReuseIdentifier:@"LessonCell"];
    [self.collectionView
        registerClass:[UICollectionReusableView class]
        forSupplementaryViewOfKind:UICollectionElementKindSectionHeader
        withReuseIdentifier:@"LessonHeader"];
    self.collectionView.backgroundColor = [UIColor myGolden];
    // ...
}

The first two of the Three Big Questions to the data source are boring and familiar:

-(NSInteger)numberOfSectionsInCollectionView:
        (UICollectionView *)collectionView {
    return [self.sectionNames count];
}
-(NSInteger)collectionView:(UICollectionView *)collectionView
        numberOfItemsInSection:(NSInteger)section {
    return [self.sectionData[section] count];
}

The third of the Three Big Questions to the data source creates and configures the cells. In a .xib file, I’ve designed the cell with a single subview, a UILabel with tag 1; if the text of that label is still @"Label", this is a sign that the cell has come freshly minted from the nib and needs further initial configuration. Among other things, I assign each new cell a selectedBackgroundView and give the label a highlightedTextColor, to get an automatic indication of selection:

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
        cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell* cell =
        [collectionView dequeueReusableCellWithReuseIdentifier:@"LessonCell"
                                                  forIndexPath:indexPath];
    UILabel* lab = (UILabel*)[cell viewWithTag:1];
    if ([lab.text isEqualToString:@"Label"]) {
        lab.highlightedTextColor = [UIColor whiteColor];
        cell.backgroundColor = [UIColor myPaler];
        cell.layer.borderColor = [UIColor brownColor].CGColor;
        cell.layer.borderWidth = 5;
        cell.layer.cornerRadius = 5;
        UIView* v = [UIView new];
        v.backgroundColor =
            [[UIColor blueColor] colorWithAlphaComponent:0.8];
        cell.selectedBackgroundView = v;
    }
    Term* term = self.sectionData[indexPath.section][indexPath.item];
    lab.text = term.lesson;
    return cell;
}

The fourth data source method asks for the supplementary element views; in my case, these are the section headers. I haven’t bothered to design the header in a nib; instead, I configure the entire thing in code. Again I distinguish between newly minted views and reused views; the latter will already have a single subview, a UILabel:

-(UICollectionReusableView *)collectionView:(UICollectionView *)collectionView
          viewForSupplementaryElementOfKind:(NSString *)kind
                                atIndexPath:(NSIndexPath *)indexPath {
    UICollectionReusableView* v =
        [collectionView
            dequeueReusableSupplementaryViewOfKind:
                UICollectionElementKindSectionHeader
            withReuseIdentifier:@"LessonHeader"
            forIndexPath:indexPath];
    if ([v.subviews count] == 0) { // no label? make one, configure
        UILabel* lab = [[UILabel alloc] initWithFrame:CGRectMake(10,0,100,40)];
        lab.font = [UIFont fontWithName:@"GillSans-Bold" size:20];
        lab.backgroundColor = [UIColor clearColor];
        [v addSubview:lab];
        v.backgroundColor = [UIColor blackColor];
        lab.textColor = [UIColor myPaler];
    }
    UILabel* lab = (UILabel*)v.subviews[0];
    lab.text = self.sectionNames[indexPath.section];
    return v;
}

As you can see from Figure 8-11, the first section is treated specially — it has no header, and its cell is wider. I take care of that with two UICollectionViewDelegateFlowLayout methods:

- (CGSize)collectionView:(UICollectionView *)collectionView
        layout:(UICollectionViewLayout*)collectionViewLayout
        sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    CGSize sz =
        ((UICollectionViewFlowLayout*)collectionViewLayout).itemSize;
    if (indexPath.section == 0)
        sz.width = 150;
    return sz;
}
- (CGSize)collectionView:(UICollectionView *)collectionView
        layout:(UICollectionViewLayout*)collectionViewLayout
        referenceSizeForHeaderInSection:(NSInteger)section {
    CGSize sz =
        [(UICollectionViewFlowLayout*)collectionViewLayout)
            headerReferenceSize];
    if (section == 0)
        sz.height = 0;
    return sz;
}

When the user taps a cell, I hear about it through the delegate method collectionView:didSelectItemAtIndexPath: and respond accordingly. That is the entire code for managing this collection view!

Here’s an example of deleting cells in a collection view. Let’s assume that the cells to be deleted have been selected, with multiple selection being possible. If there are selected cells, they are provided as an array of NSIndexPaths. My data model is once again the usual pair of parallel arrays, a mutable array of strings (sectionNames) and a mutable array of arrays (sectionData); each NSIndexPath gets me directly to the corresponding piece of data in sectionData, so I delete each piece of data in reverse order, keeping track of any arrays (sections) that end up empty. Finally, I delete the items from the collection view, and then do the same for the sections:

- (void) doDelete:(id)sender {
    // delete selected
    NSArray* arr = [self.collectionView indexPathsForSelectedItems];
    if (!arr || ![arr count])
        return;
    // sort, reverse, delete items from model, keep track of empty sections
    arr = [arr sortedArrayUsingSelector:@selector(compare:)];
    NSMutableIndexSet* empties = [NSMutableIndexSet indexSet];
    for (NSIndexPath* ip in [arr reverseObjectEnumerator]) {
        [self.sectionData[ip.section] removeObjectAtIndex:ip.item];
        if (![self.sectionData[ip.section] count])
            [empties addIndex:ip.section];
    }
    // delete items from view
    [self.collectionView performBatchUpdates:^{
        [self.collectionView deleteItemsAtIndexPaths:arr];
    } completion:^(BOOL finished) {
        // delete sections from model and then from view
        if ([empties count]) {
            [self.sectionNames removeObjectsAtIndexes:empties];
            [self.sectionData removeObjectsAtIndexes:empties];
            [self.collectionView deleteSections:empties];
        }
    }];
}

Menu handling is also completely parallel to a table view; if you want additional menu items beyond the standard Copy, Cut, and Paste, the corresponding custom selectors must be implemented in a UICollectionViewCell subclass.

Custom Collection View Layouts

To explore what’s involved in writing your own layout class, let’s introduce a simple modification of UICollectionViewFlowLayout.

By default, the flow layout wants to full-justify every row of cells horizontally, spacing the cells evenly between the left and right margins, except for the last row, which is left-aligned. Let’s say that this isn’t what you want — you’d rather that every row be left-aligned, with every cell as far to the left as possible given the size of the preceding cell and the minimum spacing between cells.

To achieve this, you’ll need to subclass UICollectionViewFlowLayout and override two methods, layoutAttributesForElementsInRect: and layoutAttributesForItemAtIndexPath:. Fortunately, we’re starting with a layout, UICollectionViewFlowLayout, whose answers to these questions are almost right. So we call super and make modifications as necessary.

The really important method here is layoutAttributesForItemAtIndexPath:, which returns a single UICollectionViewLayoutAttributes object.

If the index path’s item is 0, we have a degenerate case: the answer we got from super is right. Alternatively, if this cell is at the start of a row — we can find this out by asking whether the left edge of its frame is close to the margin — we have another degenerate case: the answer we got from super is right.

Otherwise, where this cell goes depends on where the previous cell goes, so we obtain the frame of the previous cell recursively; we propose to position our left edge a minimal spacing amount from the right edge of the previous cell. We do that by changing the frame of the UICollectionViewLayoutAttributes object. Then we return that object:

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:
        (NSIndexPath *)indexPath {
    UICollectionViewLayoutAttributes* atts =
        [super layoutAttributesForItemAtIndexPath:indexPath];
    if (indexPath.item == 0)
        return atts;
    if (atts.frame.origin.x - 1 <= self.sectionInset.left)
        return atts;
    NSIndexPath* ipPrev =
        [NSIndexPath indexPathForItem:indexPath.item-1
                            inSection:indexPath.section];
    CGRect fPrev =
        [self layoutAttributesForItemAtIndexPath:ipPrev].frame;
    CGFloat rightPrev =
        fPrev.origin.x + fPrev.size.width + self.minimumInteritemSpacing;
    CGRect f = atts.frame;
    f.origin.x = rightPrev;
    atts.frame = f;
    return atts;
}

The other method, layoutAttributesForElementsInRect:, returns an NSArray of UICollectionViewLayoutAttributes objects for all the cells and supplementary views in a rect. Again we call super and modify the resulting array so that if an element is a cell, its UICollectionViewLayoutAttributes is the result of our layoutAttributesForItemAtIndexPath::

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSArray* arr = [super layoutAttributesForElementsInRect:rect];
    for (UICollectionViewLayoutAttributes* atts in arr) {
        if (nil == atts.representedElementKind) { // it's a cell
            NSIndexPath* ip = atts.indexPath;
            atts.frame =
                [self layoutAttributesForItemAtIndexPath:ip].frame;
        }
    }
    return arr;
}

Apple supplies some further interesting examples of subclassing UICollectionViewFlowLayout. For instance, the LineLayout example (accompanying the WWDC 2012 videos) implements a single row of horizontally scrolling cells, where a cell grows as it approaches the center of the screen and shrinks as it moves away. To do this, it first of all overrides a UICollectionViewLayout method I didn’t mention earlier, shouldInvalidateLayoutForBoundsChange:; this causes layout to happen repeatedly while the collection view is scrolled. It then overrides layoutAttributesForElementsInRect: to do the same sort of thing I did a moment ago: it calls super and then modifies, as needed, the transform3D property of the UICollectionViewLayoutAttributes for the onscreen cells.

(It also overrides another UICollectionViewLayout method I didn’t mention, targetContentOffsetForProposedContentOffset:withScrollingVelocity:, which is like UIScrollViewDelegate’s scrollViewWillEndDragging:withVelocity:targetContentOffset:. This is just a nice touch so that when the user scrolls, a cell always ends up exactly centered on the screen.)

You can also subclass UICollectionViewLayout itself. The WWDC 2012 videos demonstrate a UICollectionViewLayout subclass that arranges its cells in a circle; the WWDC 2013 videos demonstrate a UICollectionViewLayout subclass that piles its cells into a single stack in the center of the collection view, like a deck of cards seen from above.

A collection view layout can be powerful and complex, but getting started writing one from scratch is not difficult. To illustrate, I’ll write a collection view layout that ignores sections and presents all cells as a simple grid of squares.

In my UICollectionViewLayout subclass, called MyLayout, the really big questions I need to answer are collectionViewContentSize and layoutAttributesForElementsInRect:. To answer them, I’ll calculate the entire layout of my grid beforehand. The prepareLayout method is the perfect place to do this; it is called every time something about the collection view or its data changes. I’ll calculate the grid of cells and express their positions as an array of UICollectionViewLayoutAttributes objects; I’ll store that array in an instance variable _atts, and I’ll store the size of the grid in an instance variable _sz:

-(void)prepareLayout {
    // how many items are there in total?
    int total = 0;
    NSInteger sections = [self.collectionView numberOfSections];
    for (int i = 0; i < sections; i++)
        total += [self.collectionView numberOfItemsInSection:i];
    // work out cell size based on bounds width
    CGSize sz = self.collectionView.bounds.size;
    CGFloat width = sz.width;
    int shortside = floor(width/50.0);
    CGFloat cellside = width/(float)shortside;
    // generate attributes for all cells
    int x = 0;
    int y = 0;
    NSMutableArray* atts = [NSMutableArray new];
    for (int i = 0; i < sections; i++) {
        int jj = [self.collectionView numberOfItemsInSection:i];
        for (int j = 0; j < jj; j++) {
            UICollectionViewLayoutAttributes* att =
                [UICollectionViewLayoutAttributes
                 layoutAttributesForCellWithIndexPath:
                 [NSIndexPath indexPathForItem:j
                                     inSection:i]];
            att.frame =
                CGRectMake(x*cellside,y*cellside,cellside,cellside);
            [atts addObject:att];
            x++;
            if (x >= shortside) {
                x = 0;
                y++;
            }
        }
    }
    self->_atts = atts;
    // generate overall grid size
    int fluff = (x == 0) ? 0 : 1;
    self->_sz = CGSizeMake(width, (y+fluff) * cellside);
}

collectionViewContentSize and layoutAttributesForElementsInRect: are obvious: I’ll just return the _sz or _atts instance variable, respectively. I’m ignoring the rect: parameter in layoutAttributesForElementsInRect:, as there is no efficiency to be gained by limiting myself to it; I have the entire array of UICollectionViewLayoutAttributes objects ready, so it is simplest and quickest to provide a pointer to it:

- (CGSize)collectionViewContentSize {
    return self->_sz;
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    return self->_atts;
}

layoutAttributesForItemAtIndexPath: is implemented by looking up the corresponding value in my _atts array:

- (UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:
        (NSIndexPath *)indexPath {
    for (UICollectionViewLayoutAttributes* att in self->_atts) {
        if ([att.indexPath isEqual:indexPath])
            return att;
    }
    return nil; // shouldn't happen
}

Finally, I implement shouldInvalidateLayoutForBoundsChange: to return YES, so that if the interface is rotated, my prepareLayout will be called again to recalculate the grid. There’s a potential source of inefficiency here: the user scrolling the collection view counts as a bounds change as well. Therefore I return NO unless the bounds width has changed:

-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
    return newBounds.size.width != self->_sz.width;
}

An astonishing and delightful feature of a collection view is that its layout object can be swapped out on the fly. You can substitute one layout for another, by calling setCollectionViewLayout:animated:completion:. The data hasn’t changed, and the collection view can identify each element uniquely and persistently, so it responds by moving every element from its position according to the old layout to its position according to the new layout — and, if the animated: argument is YES, it does this with animation! Thus the elements are seen to rearrange themselves, as if by magic.

New in iOS 7, this animated change of layout can be driven interactively (in response, for example, to a user gesture; compare Chapter 6 on interactive transitions). You call startInteractiveTransitionToCollectionViewLayout:completion: on the collection view, and a special layout object is returned — a UICollectionViewTransitionLayout instance (or a subclass thereof; to make it a subclass, you need to have implemented collectionView:transitionLayoutForOldLayout:newLayout: in your collection view delegate). This transition layout is temporarily made the collection view’s layout, and your job is then to keep it apprised of the transition’s progress (through its transitionProgress property) and ultimately to call finishInteractiveTransition or cancelInteractiveTransition on the collection view.

Furthermore, also new in iOS 7, when one collection view controller is pushed on top of another in a navigation interface, the runtime will do exactly the same thing for you, as a custom view controller transition (again, compare Chapter 6). To arrange this, the first collection view controller’s useLayoutToLayoutNavigationTransitions property must be NO and the second collection view controller’s useLayoutToLayoutNavigationTransitions property must be YES. The result is that when the second collection view controller is pushed onto the navigation controller, the collection view remains in place, and the layout specified by the second collection view controller is substituted for the collection view’s existing layout, with animation.

The effect, as the second collection view controller is pushed onto the navigation stack, is conceptually rather unsettling. Although there are two collection view controllers, and although the second view controller has a view (the collection view), and its viewDidLoad and viewWillAppear: (as well as the first view controller’s viewWillDisappear:) are called as you would expect, the same collection view is also still the first view controller’s view, and the collection view’s data source and delegate are still the first view controller. Later, after the transition is complete, the collection view’s delegate becomes the second view controller, but its data source is still the first view controller. I find this profoundly weird.

Collection Views and UIKit Dynamics

The UICollectionViewLayoutAttributes class adopts the UIDynamicItem protocol (see Chapter 4). Thus, collection view elements can be animated under UIKit dynamics. The world of the animator here is not a superview but the layout itself; instead of initWithReferenceView:, you’ll create the UIDynamicAnimator with initWithCollectionViewLayout:. The layout’s collectionViewContentSize determines the bounds of this world. Convenience methods are provided so that your code can access an animated collection view item’s layout attributes directly from the animator.

You’ll need a custom collection view layout subclass, because otherwise you won’t be able to see any animation. On every frame of its animation, the UIDynamicAnimator is going to change the layout attributes of some items, but the collection view is still going to draw those items in accordance with the layout’s layoutAttributesForElementsInRect:. The simplest solution is to override layoutAttributesForElementsInRect: so as to obtain those layout attributes from the UIDynamicAnimator. This cooperation will be easiest if the layout itself owns and configures the animator.

In this example, we’re in the layout subclass, setting up the animation. The layout subclass has a property to hold the animator, as well as a BOOL property to signal when an animation is in progress:

UIDynamicAnimator* anim =
    [[UIDynamicAnimator alloc] initWithCollectionViewLayout:self];
self.animator = anim;
self.animating = YES;
// ... configure rest of animation ...

Our implementation of layoutAttributesForElementsInRect:, if we are animating, substitutes the layout attributes that come from the animator for those we would normally return; the technique I use here relies on the fact that the animator convenience methods layoutAttributesForCellAtIndexPath: and so forth return nil if the specified item is not being animated. In this particular example, both cells and supplementary items (headers and footers) can be animated, so the two cases have to be distinguished:

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSArray* arr = [super layoutAttributesForElementsInRect:rect];
    if (self.animating) {
        NSMutableArray* marr = [NSMutableArray new];
        for (UICollectionViewLayoutAttributes* atts in arr) {
            NSIndexPath* path = atts.indexPath;
            UICollectionViewLayoutAttributes* atts2 = nil;
            switch (atts.representedElementCategory) {
                case UICollectionElementCategoryCell: {
                    atts2 =
                        [self.animator
                            layoutAttributesForCellAtIndexPath:path];
                    break;
                }
                case UICollectionElementCategorySupplementaryView: {
                    NSString* kind = atts.representedElementKind;
                    atts2 =
                        [self.animator
                            layoutAttributesForSupplementaryViewOfKind:kind
                            atIndexPath:path];
                    break;
                }
                default:
                    break;
            }
            [marr addObject: (atts2 ? atts2 : atts)];
        }
        return marr;
    }
    return arr;
}