Table Views in iOS 7 for iPhone

In this blog, we’ll take a new look at table views using Xcode 5 and iOS 7. This new version of iOS is the first to assume that we will be using storyboards rather than nib files, so if you’ve been waiting to adopt storyboards, now’s the time.

Start Xcode and create a new iPhone project using the Single View Application template. Name the project TableViewIOS7. Make sure iPhone is selected in the Devices drop down, as shown below.

Creating the file

Click Next, choose a location to save the project, and click Create to save the project.

Xcode 5 doesn’t have a template for a navigation controller app using a table view, so we’re using the single view application template. Select the Main.storyboard file in the navigator, then select the View Controller:

Select the View Controller

Press the Delete key to remove this view controller from the storyboard. (This will also remove the View Controller scene).

Drag a new UINavigationController from the object inspector to the interface builder pane:

Finding the UINavigationController

Notice that when the navigation controller is dropped into interface builder, its root view controller is already set to be a table view controller. This is just what we want:

Navigation / Table View Controllers

Next, we want to delete the ViewController.h and ViewController.m file from the project. We will be replacing this class with a subclass of UITableViewController. Make sure to click the “Move to Trash” button in the dialog (the default is to remove the references only):

Deleting the View Controller

Right-click in the navigator, and select New File… from the popup. Choose the Objective – C class type, make the new class a subclass of UITableViewController and name it TableViewController.

New Table View Controller

Click Next, and save the file in the default location.
Once again, select Main.storyboard. Select the Table View Controller entry, then change the subclass to TableViewController in the Custom Class: field as shown.

Changing the subclass

Drag a Bar Button Item to the right end of the navigation bar in the table view, and give it the Add identifier. Change the title text to “Table” as shown here:

Changing the title

Select the white prototype cell and change its identifier to “Cell” in the attributes inspector:

Changing the identifier

Drag two new UIViewControllers to the storyboard. CTRL-drag a segue from the add button to the first view controller and from the prototype cell to the second view controller. Make them both “push” segues; give the add button segue the identifier “adding” and the prototype cell segue the identifier “detail.” When you are finished, the storyboard should look something like this:

The storyboard

Now we’re ready (finally) to write some code in TableViewController.h and .m!
Modify TableViewController.h to look like this:

#import <UIKit/UIKit.h>

@interface TableViewController : UITableViewController

@property (nonatomic) NSArray *content;

@end

We no longer have to specify that content will be a strong property: by default, all Objective C object types (like an NSArray) will be created with a reference count of 1. If we wanted a weak property we would need to specify (nonatomic, weak) here. Likewise, if the nonatomic specifier is omitted, the property’s setter would include code to cache its value in threaded code (since atomic is the default).

In TableViewController.m we override the getter for the content property to instantiate it if it doesn’t already exist:

(NSArray *)content
{
    if (!_content) {
        _content = @[@"Hello", @"iOS 7", @"Table", @"View", @"Example"];
    }
    return _content;
}

Auto-synthesis was introduced in iOS 6… we no longer need to synthesize properties, as long as we follow some simple conventions. To refer to the underlying iVar for a property, we preface the property name with an underscore. “_content” means “the content property’s iVar.” In any other context, we would still use self.content to set or get the value of _content. But here, we don’t want to use self.content, because that would call the getter. This is the getter, and the last thing we want is non-terminating recursion in our getter!
Also notice the syntax for declaring an NSArray constant. Surround the objects in the array by @[…]. There is no need to terminate this list with nil. In this case, we define an NSArray of five strings. (By the way, there is a similar syntax for declaring an NSDictionary constant: @{ key, value, … }.)

Next, we’ll add an edit button to the navigation bar in viewDidLoad:

(void)viewDidLoad
{
    [super viewDidLoad];

    // Uncomment the following line to preserve selection between presentations.
    // self.clearsSelectionOnViewWillAppear = NO;
 
    // Add an edit button to the "add" button in the
    // rightBarButtonItems array.
    self.navigationItem.rightBarButtonItems =
        @[self.navigationItem.rightBarButtonItem, self.editButtonItem];
}

Note that the NSArray constant syntax simplifies this code as well. The add button item is already in the storyboard. It is represented by self.navigationItem.rightBarButtonItem. We’re just adding a self.editButtonItem to this item. When there is a single bar button, it can be referred to as …BarButtonItem (right or left), but when there are multiple items, they are contained in an array named …BarButtonItems.

Now we’ll use the UITableViewDelegate methods to populate our table view:

#pragma mark – Table view data source

(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Return the number of sections.
    return 1;
}

(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    return self.content.count;
}

(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
   
    // Configure the cell…
    cell.textLabel.text = [self.content objectAtIndex:indexPath.row];
   
    return cell;
}

These three methods are all TableViewDataSource methods. The first specifies the number of sections we will display, the second is the number of rows in each section, and the third sets the text for each cell. Cells in table views are reused: visible cells are placed in a queue. When a visible cell is scrolled off the screen, it is marked for reuse. In this way, the queue contains only a few more cells than are actually visible at a time, even if the underlying data source contains thousands of entries.

Next, we write code to handle the deletion of a row in the tableView:

commitEditingStyle: forRowAtIndexPath: method:
(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        // Delete the row from the data source first…
        @autoreleasepool {
            NSMutableArray *tempContent = [self.content mutableCopy];
            [tempContent removeObject:[tempContent                                                                objectAtIndex:indexPath.row]];
            self.content = tempContent;
        }
        //now delete the row from the table view:
        [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
    }  
    else if (editingStyle == UITableViewCellEditingStyleInsert) {
        // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
    }  
}

We must delete the correct element from the _content array before we delete the actual table view row. Since _content is an immutable array, we need a mutable copy. To avoid any chance that tempContent will leak, we wrap its use in an @autoreleasepool block. Once we have tempContent, we remove the object that corresponds to the cell’s indexPath.row, then set _content back to the tempContent array. After that, the given template code handles the actual row deletion for us.

The final method we must deal with is prepareForSegue: sender:. In previous versions of Xcode, the template did not include this method. Instead, the tableView: didSelectRowAtIndexPath: method was provided. When dealing with storyboard segues, prepareForSegue: sender: is a better choice because there will likely be more than one segue triggering the display of a view controller (as there is in this case). We need to deal with the adding segue as well as the detail segue; we can handle both using this method:

(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
    if ([segue.identifier isEqualToString:@"adding"]) {
        UIViewController *destination =
            (UIViewController *)segue.destinationViewController;
        destination.title = @"Add a new word";
    } else if ([segue.identifier isEqualToString:@"detail"]) {
        UIViewController *destination =
            (UIViewController *)segue.destinationViewController;
        UITableViewCell *cell = (UITableViewCell *)sender;
        destination.title = cell.textLabel.text;
    } else {
        NSLog(@"ERROR: Unknown segue identifier!");
        exit(1);
    }
}

Both the segue.destinationViewController property and the sender parameter come into the method as id types: they must be cast to the appropriate types before they can be used. In this example, we are using a generic UIViewController as the destinationViewController. In production code, there would be a UIViewController subclass for both the adding and detail view controllers, and we would cast destinationViewController to those subclasses.

In the case of the adding view controller, we just set the title to “Add a new word.” But in the case of the detail view controller, we want to pass the cell’s text to the title property of the view controller. The key to doing this is to realize that sender in this case is the cell that sent the segue message; therefore we should cast sender to type UITableViewCell. Once we’ve done that, it’s trivial to set the title text of the view controller.

Running the app 1
Running the app 2
Running the app 3

Things to try:

  1. Add a new UIViewController subclass to the project to back the adding view. Name the class AddingController. Use a UITextField to get a new word from the user, then use delegation back to the TableViewController to add the word to the content array. Make sure to refresh the table view after you have added the new word.
  2. Add a new UIViewController instance to the project to back the detail view. Name this class DetailController. Change the content property to an NSDictionary object in which the keys are the words and the values are a short definition of each word. In DetailController, display the word in the title bar and the definition in a UILabel control.
  3. Have Fun!

Leave a Reply

Your email address will not be published. Required fields are marked *