Chapter 10
Tables Using UITableView and UITableViewController

Key Skills & Concepts

• Understanding table views

• Understanding table view delegates

• Understanding table view data sources

• Grouping and indexing table rows

• Selecting table rows

• Modifying a table’s appearance

• Using a table in a navigation controller

• Editing a table’s rows

Table views display content in a list. Tables have a single column and multiple rows. They can scroll vertically and display large data sets. For example, the Notes application is a good example of an application containing a table. Notes’ first screen is a list consisting of zero or more notes. In Figure 10-1, the list contains three notes. Each row presents the note’s text, its transcription time, and a disclosure arrow.

Image

Figure 10-1 The Notes application consists of a UITableView and UINavigationBar.

Image

Figure 10-2 Creating a new note using the Notes application

The disclosure arrow indicates that details are on the next screen. Upon tapping a row, the application takes the user to the detail view for that particular row’s note (Figure 10-2).

Tables can be grouped and indexed. For instance, the Music application on an iPod touch uses an index (Figure 10-3). The Settings application’s rows are grouped (Figure 10-4).

Image

Figure 10-3 The Music application on an iPod touch uses a UITableView with an index.

Image

Figure 10-4 The Settings application uses a grouped UITableView.

Table rows might also contain a picture and other customizations, as the YouTube and App Store applications illustrate (Figure 10-5).

As the applications in the first five figures illustrate, the table view is a powerful control for listing items. You can modify rows, add images, select rows, and edit them. In this chapter, you learn how to use tables. You learn how to build a table, change its style to grouped, add an index to it, and accessorize it. You also learn how to place a table in a navigation controller and how to edit a table’s rows. It is a long chapter, but the table view is a powerful control.

Image

Figure 10-5 The YouTube and App Store applications use images and custom cells.

UITableView

The UITableView class represents a table view. This class is for displaying and editing information lists. It consists of a single column of multiple rows. Users can scroll vertically to navigate through a table’s rows. Each row contains a cell. You can customize that cell’s appearance considerably.

You can index tables and create tables with zero or more sections. When you create a table, you have a choice of two styles: UITableViewStylePlain or UITableViewStyleGrouped. A plain table style presents a table like that in Figure 10-3. A grouped table presents a table like that in Figure 10-4. You see examples implementing both styles later in this chapter.

UITableView classes have an associated UITableViewController, a UITableViewDelegate, and a UITableViewDataSource. The UITableViewController is the controller class for a table view. You create an instance of this class to manage the UITableView. The UITableViewDelegate is a protocol you adopt in a custom class you write. This protocol allows you to manage selections, configure headers and footers, and manage cells. The UITableViewDataSource is also a protocol you adopt in a custom class. This protocol allows you to manage a table’s data source.

UITableViewDelegate and UITableViewDataSource

The UITableViewDelegate and UITableViewDataSource are protocols at least one class in your application must adopt if your application contains a UITableView. You can create your own custom classes to adopt these protocols, or create a UITableViewController that automatically adopts these protocols. If you choose to use a UITableViewController rather than custom classes, you simply connect the table view’s dataSource and delegate outlets to the UITableViewController. You can then implement both protocols’ methods in the UITableViewController.

UITableViewDelegate

A UITableView’s delegate adopts the UITableViewDelegate protocol. This protocol manages selections, headers, footers, and other tasks. Table 10-1 lists the methods covered in this chapter.

UITableViewDataSource

A UITable’s data source adopts the UITableViewDataSource protocol. A table’s data source provides the table with its data. Table 10-2 lists the UITableViewDataSource protocol methods covered in this chapter.

Image

Table 10-1 UITableViewDelegate Methods in This Chapter

The methods tableView:numberOfRowsInSection: and tableView:cellForRowAtIndexPath: are required methods that every table’s data source must implement.

-(NSInteger)tableView:(UITableView *) tableView numberOfRowsInSection:
(NSInteger) section
-(UITableViewCell *)tableView:(UITableView *) tableView
cellForRowAtIndexPath: (NSIndexPath *) indexPath

The tableView:numberOfRowsInSection: method informs the data source how many rows a section contains. The tableView:cellForRowAtIndexPath: method provides a table view with its cells.

Image

Table 10-2 UITableViewDataSource Methods in This Chapter

Try This: Adopting the UITableViewDelegate and UITableViewDataSource

In this first task, you create a UIViewController and have it manage the table view. You also implement a custom class that adopts the UITableViewDelegate and UITableViewDataSource.

Creating an Empty Table

1. Create a new Window-based application. Name the application TableProjectOne.

2. Create a new UIViewController subclass. Name the class MyViewController. Ensure that the check box is checked to create a xib file to go with it.

3. Open MyViewController.xib, click on the view icon to make it visible, and then drag a table view into the view (Figure 10-6). It will automatically resize to completely fill the view.

4. Save your changes.

Image

Figure 10-6 Adding a UITableView to a view

5. Add a MyViewController as an IBOutlet to TableProjectOneAppDelegate (Listings 10-1 and 10-2).

Listing 10-1 TableProjectOneAppDelegate.h

#import <UIKit/UIKit.h>
@class MyViewController;
@interface TableProjectOneAppDelegate : NSObject
<UIApplicationDelegate> {
 UIWindow *window;
 MyViewController *viewController;
}
@property (nonatomic, retain) IBOutlet MyViewController
*viewController;
@property (nonatomic, retain) IBOutlet UIWindow *window;
@end

Listing 10-2 TableProjectOneAppDelegate.m

#import "TableProjectOneAppDelegate.h"
#import "MyViewController.h"
@implementation TableProjectOneAppDelegate
@synthesize window;
@synthesize viewController;
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [window addSubview:self.viewController.view];
    [window makeKeyAndVisible];
    return YES;
}

- (void)dealloc {
    [viewController release];
    [window release];
    [super dealloc];
}
@end

6. Modify application:didFinishLaunchingWithOptions so that it adds the viewController property’s view. Build the application.

7. Open MainWindow.xib and drag a View Controller to the editing pane. Change its class and Nib Name to MyViewController.

Image

Figure 10-7 An application with an empty UITableView

8. Connect the TableProjectOneAppDelegate’s viewController outlet to the MyViewController you just added.

9. Save your changes and run the application. The application loads an empty table into the iPhone Simulator (Figure 10-7).

NOTE
In Steps 1–9, you added the table view as a subview to the view in the MyViewController nib. If you preferred, you could omit the extra view and just add the table view directly as the nib’s view. But note, you would also change MyViewController from a UIViewController to a UITableViewController. Point is, you don’t have to put the UITableView inside a UIView in order to use it. A UITableView can be added directly to the XIB and connected to a table view controller’s view outlet, and it will work the same way. The next Try This example illustrates.

In this task’s first step, you created a UITableView as a subview of a UIView. When the UIViewController loads and displays its view, the view automatically displays its subview, the table view. But all it loads is an empty table. To actually load data into the table, you must implement a delegate and a data source. Moreover, the data source must actually provide data for the table. In the next few steps, you create a delegate and data source for the table.

Try This: Adding a Delegate and Data Source

1. Create a new class named TableHandler, derived from NSObject. Change TableHandler so that it adopts the UITableViewDelegate and UITableViewDataSource protocols (Listings 10-3 and 10-4).

Listing 10-3 TableHandler.h

#import <Foundation/Foundation.h>
@interface TableHandler : NSObject <UITableViewDelegate,
UITableViewDataSource> {
 NSArray * tableDataList;
}
@property (nonatomic, retain) NSArray * tableDataList;
-(void) fillList;
@end

Listing 10-4 TableHandler.m

#import "TableHandler.h"
@implementation TableHandler
@synthesize tableDataList;
-(void) fillList {
NSArray * tempArray = [[[NSArray alloc] initWithObjects:@"Item One",
@"Item Two", @"Item Three", @"Item Four", @"Item Five", @"Item Six",
@"Item Seven", @"Item Eight", @"Item Nine", @"Item Ten", @"Item Eleven",
@"Item Twelve", @"Item Thirteen", @"Item Fourteen", @"Item Fifteen",
@"Item Sixteen", @"Item Seventeen", @"Item Eighteen", @"Item Nineteen",
@"Item Twenty", nil] autorelease];
self.tableDataList = tempArray;
}
#pragma mark -
#pragma mark UITableViewDataSource Protocol Methods
-(NSInteger) tableView : (UITableView *) tableView
numberOfRowsInSection: (NSInteger) section
{
 return [self.tableDataList count];
}

-(UITableViewCell *) tableView : (UITableView *) tableView
    cellForRowAtIndexPath: (NSIndexPath *) indexPath {
 UITableViewCell *cell =
    [tableView dequeueReusableCellWithIdentifier: @"acell"];
 if(cell == nil) {
  cell = [[[UITableViewCell alloc]
         initWithStyle:UITableViewCellStyleDefault
         reuseIdentifier:@"acell"] autorelease];
 }
 cell.textLabel.text = [self.tableDataList
    objectAtIndex:[indexPath row]];
 return cell;
}

#pragma mark -
-(void)dealloc {
 [tableDataList release];
 [super dealloc];
}
@end

2. Add an NSArray property and a method named fillList for filling the array.

3. Implement the fillArray method so that the application has data to load into the table’s cells.

4. Implement the tableView:numberOfRowsInSection: and tableView: cellForRowAtIndexPath: methods.

5. Modify MyViewController to have a TableHandler property (Listings 10-5 and 10-6). Ensure the property is an IBOutlet.

Listing 10-5 MyViewController.h

#import <UIKit/UIKit.h>
#import "TableHandler.h"
@interface MyViewController : UIViewController {
 TableHandler * myHandler;
}
@property (nonatomic, retain) IBOutlet TableHandler * myHandler;
@end

Listing 10-6 MyViewController.m

#import "MyViewController.h"
@implementation MyViewController
@synthesize myHandler;
-(void) viewDidLoad {
 [self.myHandler fillList];
}
-(void)dealloc {
 [self.myHandler release];
 [super dealloc];
}
@end

6. Implement the viewDidLoad method in MyViewController so that it calls its TableHandler’s fillList method to ensure that the table has data before it needs to draw.

7. Save your changes and build the project.

8. Open MyViewController.xib and drag an object to the editing pane. Change the object’s class to TableHandler (Figure 10-8).

9. Connect the File’s Owner myHandler outlet to the newly added TableHandler (Figure 10-9).

10. Connect the table view’s dataSource and delegate outlets to the newly added TableHandler.

11. Save and click Run. The application displays 20 rows (Figure 10-10).

Image

Figure 10-8 Adding a TableHandler object in Interface Builder

Image

Figure 10-9 Connecting the controller to TableHandler

Image

Figure 10-10 The project running in iPhone Simulator

NOTE
The #pragma mark lines in the code listings for this example are a handy way to organize your code as your implementation files get larger. Click the file or function name displayed across the top of the editing pane and you’ll see #pragma mark entries in the pull-down menu that appears. This lets you quickly jump around in a large file.

UITableViewController

The UITableViewController manages a table view. The UITableView can use objects defined in a table’s nib to define a table’s delegate and data source, or it can use itself as the delegate and data source. For instance, in the previous example, you set the table’s delegate and data source properties to the TableHandler class. You could have added a UITableViewController, set it as the table’s File’s Owner, and then set its outlets to TableHandler.

If you do not provide a delegate and data source in a table’s nib, a UITableViewController sets its data source and delegate to itself. By doing this, the UITableViewController saves you the work of having to create your own classes so that they adopt the delegate and data source. You still must implement any data source and delegate methods desired. However, rather than implementing these methods in separate custom classes, you implement them in a UITableViewController subclass. The UITableViewController then functions as the table’s controller, delegate, and data source.

Try This: Using a UITableViewController

In the last Try This task, you did things the hard way. However, the task’s purpose was to illustrate adding a table view as a subview, with no controller. In this Try This task, you use a UITableViewController. Moreover, rather than adding a table view as a subview, you add it directly to the xib as the primary view.

1. Create a new Window-based Application. Name the application TableProjectTwo.

2. Create a new UITableViewController subclass (select the UIViewController subclass and then check the UITableViewController subclass check box). Name the class MyTableViewController. Make sure that it is also creating the XIB file.

3. Open MyTableViewController.xib. You’ll notice that Xcode created a table view for you and connected it to the File’s Owner. It also connected the table view’s dataSource and delegate outlets to the File’s Owner (Figure 10-11).

Image

Figure 10-11 XIB automatically created for a UITableViewController subclass

4. Add a MyTableViewController as an IBOutlet to TableProjectTwoAppDelegate. Do not forget to import MyTableViewController. Modify application:didFinishLaunchingWithOptions so that it adds the viewController property’s view (Listings 10-7 and 10-8).

Listing 10-7 TableProjectTwoAppDelegate.h

#import <UIKit/UIKit.h>
#import "MyTableViewController.h"
@interface TableProjectTwoAppDelegate : NSObject
<UIApplicationDelegate> {
 UIWindow *window;
 MyTableViewController *viewController;
}
@property (nonatomic, retain) IBOutlet MyTableViewController *
viewController;
@property (nonatomic, retain) IBOutlet UIWindow *window;
@end

Listing 10-8 TableProjectTwoAppDelegate.m

#import "TableProjectTwoAppDelegate.h"
@implementation TableProjectTwoAppDelegate
@synthesize window;
@synthesize viewController;
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [window addSubview:self.viewController.view];
    [window makeKeyAndVisible];
    return YES;
}
-(void)dealloc {
 [window release];
 [viewController release];
 [super dealloc];
}

Listing 10-9 MyTableViewController.h

#import <Foundation/Foundation.h>
@interface MyTableViewController : UITableViewController {
 NSArray * tableDataList;
}
@property (nonatomic, retain) NSArray * tableDataList;
@end

5. Edit MyTableViewController.h and add the tableDataList value (Listing 10-9).

6. Edit MyTableViewController.m to replace the delegate methods required by the table with the methods in Listing 10-10.

Listing 10-10 MyTableViewController.m

#import "MyTableViewController.h"
@implementation MyTableViewController
@synthesize tableDataList;

-(void) viewDidLoad {
 NSArray * tempArray = [[[NSArray alloc] initWithObjects:@"Item One",
  @"Item Two", @"Item Three", @"Item Four", @"Item Five", @"Item Six",
  @"Item Seven", @"Item Eight", @"Item Nine", @"Item Ten", @"Item Eleven",
  @"Item Twelve", @"Item Thirteen", @"Item Fourteen", @"Item Fifteen",
  @"Item Sixteen", @"Item Seventeen", @"Item Eighteen", @"Item Nineteen",
  @"Item Twenty", nil] autorelease];

 self.tableDataList = tempArray;
}

-(NSInteger) tableView : (UITableView *) tableView
    numberOfRowsInSection: (NSInteger) section {
 return [self.tableDataList count];
}

-(UITableViewCell *) tableView : (UITableView *) tableView
    cellForRowAtIndexPath: (NSIndexPath *) indexPath {
 UITableViewCell *cell =
    [tableView dequeueReusableCellWithIdentifier:@"acell"];
 if(cell == nil) {
  cell = [[[UITableViewCell alloc] initWithStyle:
        UITableViewCellStyleDefault reuseIdentifier:@"acell"] autorelease];
 }
 cell.textLabel.text = [self.tableDataList
                       objectAtIndex:[indexPath row]];
 return cell;
}

-(void)dealloc {
 [tableDataList release];
 [super dealloc];
}
@end

7. Open MainWindow.xib and drag a table view controller to the document window. Change its class and Nib Name to MyTableViewController. Since it will be loaded from the separate XIB file, you can delete the TableView that was automatically created under MyTableViewController when you dragged it to the editing pane.

8. Connect the TableProjectTwoAppDelegate’s viewController outlet to the MyTableViewController added to the document window.

9. Save and run the application. The results should match the previous task’s results (Figure 10-10).

Grouping and Indexing

Tables have two main styles: grouped and plain. Figure 10-3 illustrates a table with a plain style, while Figure 10-4 illustrates a table with a grouped style. Plain tables might also be indexed. An index sorts the rows and makes navigation quicker by letting a user jump to different locations in the index.

Grouped Table Style

Grouping is accomplished by changing a UITableView’s style to grouped. You then tell a UITableView’s data source how many sections belong in the table and how many rows belong in each section. The class adopting the UITableViewDataSource protocol informs the table how many sections via the numberOfSectionsInTableView: method. It informs the table how many rows are in each section via the tableView:numberOfRowsInSection: method.

-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
-(NSInteger)tableView:(UITableView *) tableView numberOfRowsInSection:
(NSInteger)section

Each grouping might have a title. You add titles by having your UITableViewDataSource protocol adoption implement the tableView:titleForHeaderInSection method. This method provides a table view with a title for a section’s header. You might also wish to add a footer to each grouping by implementing the tableView:titleForFooterInSection: method.

-(NSString *)tableView:(UITableView *)tableView titleForHeaderIn
Section:(NSInteger)section
-(NSString *)tableView:(UITableView *)tableView titleForFooterIn
Section:(NSInteger)section

Try This: Grouping

1. Copy TableProjectOne from the first task to a new location. Open the newly copied TableProjectOne in Xcode.

2. Modify the array in TableHandler so that tableDataList consists of an array of five arrays (Listing 10-11).

Listing 10-11 Table’s list modified to an array of arrays

-(void) fillList {
NSArray * tempArrayA = [[[NSArray alloc] initWithObjects:@"AItem One",
@"AItem Two", nil] autorelease];
NSArray * tempArrayB = [[[NSArray alloc] initWithObjects:@"BItem Three",
@"BItem Four", nil] autorelease];
NSArray * tempArrayC = [[[NSArray alloc] initWithObjects:@"CItem Five",
@"CItem Six", nil] autorelease];
NSArray * tempArrayD = [[[NSArray alloc] initWithObjects:@"DItem Seven",
@"DItem Eight", nil] autorelease];
NSArray * tempArrayE = [[[NSArray alloc] initWithObjects:@"EItem Nine",
@"EItem Ten", nil] autorelease];
NSArray * tempArray = [[[NSArray alloc] initWithObjects:tempArrayA,
tempArrayB, tempArrayC, tempArrayD, tempArrayE, nil] autorelease];
 self.tableDataList = tempArray;
}

3. Add the numberOfSectionsInTableView and titleForHeaderInSection methods to TableHandler.m (Listing 10-12).

Listing 10-12 Modifications to TableHandler.m to support grouping

-(NSInteger) numberOfSectionsInTableView: (UITableView *) tableView {
 return [tableDataList count];
}
-(NSString *) tableView: (UITableView *) tableView
titleForHeaderInSection: (NSInteger) section {
 switch (section) {
 case 0: return @"A"; break;
 case 1: return @"B"; break;
 case 2: return @"C"; break;
 case 3: return @"D"; break;
 case 4: return @"E"; break;
 }
 return nil;
}
-(NSInteger) tableView : (UITableView *) tableView numberOfRowsInSection:
(NSInteger) section {
 return [[tableDataList objectAtIndex: section] count];
}

4. Modify numberOfRowsInSection to return [[tableDataList objectAtIndex: section] count] and modify numberOfSections to return [tableDataList count].

5. Modify cellForRowAtIndexPath so that it uses the section and the row (Listing 10-13).

Listing 10-13 The cellForRowAtIndexPath modified to use row and section

-(UITableViewCell *) tableView: (UITableView *) tableView
    cellForRowAtIndexPath: (NSIndexPath *) indexPath {
 UITableViewCell *cell =
    [tableView dequeueReusableCellWithIdentifier: @"acell"];
 if (cell == nil) {
  cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
  reuseIdentifier: @"acell"] autorelease];
 }
 cell.textLabel.text = [[self.tableDataList objectAtIndex:indexPath.section]
 objectAtIndex:indexPath.row]; return cell;
}

6. Select MyViewController.xib to edit it. Change the table view’s Style from Plain to Grouped (Figure 10-12).

7. Save and click Run. The table’s rows are grouped (Figure 10-13).

Image

Figure 10-12 Changing the table to grouped

Image

Figure 10-13 The application running in iPhone Simulator

The fillList method initializes an NSArray of five NSArrays. The tableView:titleForHeader InSection is hard-coded to return A, B, C, D, or E, depending upon the current section. When the table loads, five sections are created, each with a title from the titleForHeaderInSection method. Each row’s content is determined from the cellForRowAtIndexPath method.

Indexing

Tables can be indexed. To index a table, a table’s style should be plain. As with grouping, you implement the numberOfSectionsInTableView: and tableView:numberOfRowsInSection: methods, but you also implement a third method: sectionIndexTitlesForTableView:. This method, implemented in your UITableViewDataSource adoptee, creates the titles that appear along a table’s right side. Upon clicking one of these values, a user is taken directly to the group with the corresponding title.

-(NSArray *) sectionIndexTitlesForTableView:(UITableView *) tableView

Try This: Indexing

1. Open the TableProjectOne from the grouping Try This task you just completed.

2. Open MyViewController.xib and change the table view’s style to Plain.

3. Save and click Run. Notice the application is still grouped, but the table’s appearance is changed (Figure 10-14).

4. Implement the sectionIndexTitlesForTableView method, as in Listing 10-14, in TableHandler.m.

Listing 10-14 The sectionIndexTitlesForTableView method

-(NSArray *) sectionIndexTitlesForTableView:
    (UITableView *) tableView {
 NSArray * keys = [[[NSArray alloc] initWithObjects: @"A", @"B", @"C",
                         @"D", @"E",nil] autorelease];
 return keys;
}

5. Click Run. The application has an index (Figure 10-15).

Image

Figure 10-14 A plain table with groupings

Image

Figure 10-15 The indexed table running in iPhone Simulator

NOTE
Don’t let this chapter’s reliance on simple NSArrays fool you into thinking you must use this collection class to hold a table’s data. You can use any collection class, retrieve the data from a database, or use any other method you deem appropriate, provided you implement methods to obtain the data correctly.

Images in Tables

Adding an image to a table, provided you are happy with the image appearing in the row’s upper-left corner, is not difficult. Simply add an image to the cell in the tableView:cellForRowAtIndexPath: method. Remember, this method loads a UITableViewCell into a row. You can use this method to initialize a table cell with an image using the UITableViewCell’s imageView.image property. The image is then displayed in the table’s row.

Try This: Adding an Image

1. Open the last task’s project. From the book’s Resources folder, add the images power.png, icon.png, package.png, web.png, and colorize.png to the project’s Resources folder.

2. Modify the tableView:cellForRowAtIndexPath: method to match Listing 10-15.

Listing 10-15 The tableView:cellForRowAtIndexPath: method added to TableHandler.m

-(UITableViewCell *) tableView : (UITableView *) tableView
    cellForRowAtIndexPath: (NSIndexPath *) indexPath {
 UITableViewCell *cell =
    [tableView dequeueReusableCellWithIdentifier: @"acell"];
 if(cell == nil) {
  cell = [[[UITableViewCell alloc] initWithStyle:
  UITableViewCellStyleDefault reuseIdentifier:@"acell"] autorelease];
 }
 cell.textLabel.text = [[self.tableDataList objectAtIndex:
    indexPath.section] objectAtIndex: indexPath.row];
 UIImage * image;
 switch (indexPath.section) {
  case 0: image = [UIImage imageNamed:@"power.png"]; break;
  case 1: image = [UIImage imageNamed:@"Icon.png"]; break;

  case 2: image = [UIImage imageNamed:@"package_graphics.png"]; break;
  case 3: image = [UIImage imageNamed:@"colorize.png"]; break;
  case 4: image = [UIImage imageNamed:@"web.png"]; break;
 }
 cell.imageView.image = image;
 return cell;
}

3. Click Run. Notice that iOS automatically scales any images that are too large so that they don’t overrun the table cell bounds (Figure 10-16).

Image

Figure 10-16 Images display on left side of each table row.

Selecting Rows

The UITableView allows you to select a row. Typically, a table presents rows to a user, where each row is an item in a hierarchical list. Upon selecting a row, the user is generally taken to another view presenting the item’s details. You implement the tableView:willSelectRowAtIndex: and tableView:didSelectRowAtIndexPath: methods if you wish to allow a user to select a row.

-(NSIndexPath *) tableView:(UITableView *) tableView
willSelectRowAtIndexPath: (NSIndexPath *) indexPath
-(void) tableView:(UITableView *) tableView didSelectRowAtIndexPath:
(NSIndexPath *) indexPath

The tableView:willSelectRowAtIndexPath: method allows the delegate to react to a row about to be selected. The tableView:didSelectRowAtIndexPath: method allows a reaction to a row’s selection. These methods are useful because they allow you to implement custom behavior in a table’s delegate when a row is selected.

Try This: Row Selection

1. Open the last task’s project in Xcode.

2. Add the tableView:willSelectRowAtIndexPath: and the tableView:didSelectRowAtIndexPath: methods in Listing 10-16 to TableHandler.m.

Listing 10-16 The tableView:willSelectRowAtIndexPath: and tableView:didSelectRow AtIndexPath: methods

-(NSIndexPath *) tableView: (UITableView *) tableView
     willSelectRowAtIndexPath: (NSIndexPath *) indexPath {
  NSLog(@"Item at the %d array and %d item has value: %@",
    indexPath.section, indexPath.row,
    [[self.tableDataList objectAtIndex: indexPath.section]
       objectAtIndex: indexPath.row]);
  return indexPath;
}

-(void)tableView:(UITableView *) tableView didSelectRowAtIndexPath:
    (NSIndexPath *) index {
 NSLog(@"Yes I did....");
}

3. Click Run. The application looks the same as the previous task’s results. Clicking the items should result in logging similar to Listing 10-17.

Listing 10-17 Debugger Console logging

2010-08-31 20:27:54.220 TableProjectOne[53965:207] I selected the item
at the 0 array and 0 item, the value is: AItem One
2010-08-31 20:27:54.222 TableProjectOne[53965:207] Yes I did....
2010-08-31 20:27:56.900 TableProjectOne[53965:207] I selected the item
at the 1 array and 0 item, the value is: BItem Three
2010-08-31 20:27:56.901 TableProjectOne[53965:207] Yes I did....

Changing Row Height

A couple of sections ago, you added images that were too large to display in a table’s row. iOS resized the images on the fly, so this wasn’t a big issue. However, sometimes you want to display a larger image, text with a large font, or a custom cell. One way you can add space in a table’s row is by increasing the row’s height. You increase a row’s height using the heightForRowAtIndexPath method.

Try This: Changing Row Height

1. Open the previous task project in Xcode.

2. Add the heightForRowAtIndexPath: method in Listing 10-18 to TableHandler.m.

Listing 10-18 The heightForRowAtIndexPath method added to TableView

-(CGFloat) tableView : (UITableView *) tableView
heightForRowAtIndexPath: (NSIndexPath *) indexPath {
return 90;
}

3. Click Build And Go, and the rows are 90 pixels tall (Figure 10-17).

Image

Figure 10-17 The application running with 90-pixel-high rows in iPhone Simulator

Accessorizing Table Cells

You can tell a cell to place a check mark, disclosure arrow, or detailed disclosure button in the cell by setting the cell’s accessoryView or accessoryType properties. Accessories are visual user interface clues for users. For instance, the check mark might inform a user that he or she has already selected a row. A disclosure arrow or a detailed disclosure button might hint to a user that he or she can select the row to view the row’s details in another view. The check mark and disclosure button are visual clues for a user and do not provide events that respond to a user tapping them. However, the detailed disclosure button can respond to a user tapping it.

Try This: Accessorizing a Table Cell

1. Open the previous project in Xcode and comment out the sectionIndexTitlesForTableView method in TableHandler.m.

2. Add the tableView:accessoryButtonTappedForRowWithIndexPath: method to TableHandler.m (Listing 10-19). Also modify the tableView:cellForRowAtIndexPath: method to add the accessories to the table cells.

Listing 10-19 The tableView:cellForRowAtIndexPath: and tableView:accessoryButton TappedForRowWithIndexPath: methods

-(UITableViewCell *) tableView : (UITableView *) tableView
    cellForRowAtIndexPath: (NSIndexPath *) indexPath {
 NSUInteger section = [indexPath section];
 UITableViewCell *cell =
    [tableView dequeueReusableCellWithIdentifier: @"acell"];
 if(cell == nil) {
  cell = [[[UITableViewCell alloc] initWithStyle:
  UITableViewCellStyleDefault reuseIdentifier:@"acell"] autorelease];
  }
 cell.textLabel.text = [[self.tableDataList objectAtIndex:section]
    objectAtIndex:[indexPath row]];
 UIImage * image;
 switch (indexPath.section) {
  case 0: image = [UIImage imageNamed:@"power.png"]; break;
  case 1: image = [UIImage imageNamed:@"Icon_resize.png"]; break;
  case 2: image = [UIImage imageNamed:@"package_graphics_resize.png"];
    break;
  case 3: image = [UIImage imageNamed:@"colorize.png"]; break;
  case 4: image = [UIImage imageNamed:@"web.png"]; break;
  }
 cell.imageView.image = image;
 if(indexPath.section == 0) {
  cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
 } else if(indexPath.section == 1) {
    cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
  } else {
  cell.accessoryType = UITableViewCellAccessoryCheckmark;
  }
 return cell;
}

-(void) tableView:(UITableView *) tableView
    accessoryButtonTappedForRowWithIndexPath: (NSIndexPath *) indexPath {
 NSLog(@"hey now....");
}

Image

Figure 10-18 Accessorized table rows

3. Click Run. The rows contain accessories (Figure 10-18). When you tap the detailed disclosure button, the table calls the tableView:accessoryButtonTappedForRow WithIndexPath: method.

Customizing a Table Cell

Tables can be customized. Sometimes you might wish to format data differently than provided by the default UITableViewCell. The most direct way to accomplish formatting a table cell is by adding a view to the UITableViewCell. You do this in code, by adding a subview to a cell when initializing it in the tableView:cellForRowAtIndexPath method. Another technique is to create a subclass of a UITableViewCell and then create its graphical layout using Interface Builder.

Try This: Customizing a Cell Using Interface Builder

1. Create a new Navigation-based Application. Name the application CustomizedCell.

2. Create a new subclass of UITableViewCell. Note that Xcode provides a template for this class (select Objective-C class and then UITableViewCell from the pull-down). Name the class MyCellView.

3. In MyCellView, create a new UILabel outlet named theTextLabel (Listings 10-20 and 10-21).

Listing 10-20 MyCellView.h

#import <UIKit/UIKit.h>
@interface MyCellView : UITableViewCell {
 UILabel * theTextLabel;
}
@property (nonatomic, retain) IBOutlet UILabel * theTextLabel;
@end

Listing 10-21 MyCellView.m

#import "MyCellView.h"
@implementation MyCellView
@synthesize theTextLabel;
- (id)initWithStyle:(UITableViewCellStyle)style
      reuseIdentifier:(NSString *)reuseIdentifier {
 if ((self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])) {
  // Initialization code
 }
 return self;
}

- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
 [super setSelected:selected animated:animated];
}
- (void)dealloc {
 [theTextLabel release];
 [super dealloc];
}
@end

4. Drag the image money.png from this book’s Resources folder to the Resources folder in the project. Save.

5. Create a new Empty XIB. Name the file MyCellView.xib and select it to view it in Interface Builder.

6. Add a table view cell from the library to the editing pane.

7. Change the cell’s class from UITableViewCell to MyCellView.

8. Click My Cell View in the editing pane to select it.

9. In the Attributes Inspector, select money.png as the cell’s image. Change the cell’s background to yellow. Also, be certain to set the Identifier to MyCell (Figure 10-19).

CAUTION
If you do not set a cell’s identifier, the cell can never be reused. This means every call to tableView:cellForRowAtIndexPath: results in a new table cell.

10. Drag a UIImageView and two UILabels to the canvas (Figure 10-20). Type some text in one label; delete the other label’s text. Set the image view’s image to money.png.

Image

Figure 10-19 Setting the identifier to MyCell

Image

Figure 10-20 Adding UIImageView and UILabels to a view canvas

11. Connect the MyCellView’s theTextLabel outlet to the newly added label.

12. Save your changes.

13. Open RootViewController.xib and change the table view’s background to yellow. Save your changes.

14. Modify RootViewController to implement the UITableViewController methods needed so that they match Listings 10-22 and 10-23.

Listing 10-22 RootViewController.h

#import <UIKit/UIKit.h>
#import "MyCellView.h"
@interface RootViewController : UITableViewController { }
@end

Listing 10-23 RootViewController.m

#import "RootViewController.h"
#import "CustomizedCellAppDelegate.h"
@implementation RootViewController
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
  return 1;
}

-(NSInteger)tableView: (UITableView *) tableView
        numberOfRowsInSection: (NSInteger) section {
  return 5;
}

-(CGFloat) tableView : (UITableView *) tableView
        heightForRowAtIndexPath: (NSIndexPath *) indexPath {
  return 110;
}

-(UITableViewCell *)tableView:(UITableView *) tableView
        cellForRowAtIndexPath: (NSIndexPath *) indexPath {
  MyCellView *cell = (MyCellView *) [tableView
        dequeueReusableCellWithIdentifier: @"MyCell"];
  if(cell == nil) {
    cell = [[[NSBundle mainBundle] loadNibNamed:
            @"MyCellView" owner:self options:nil] objectAtIndex:0];
  }
  [cell.theTextLabel setText:@"Just some static text."];
  NSString *imagePath = [[NSBundle mainBundle]
        pathForResource:@"money" ofType:@"png"];
  cell.imageView.image = [UIImage imageWithContentsOfFile:imagePath];
  [cell setAccessoryType:UITableViewCellAccessoryCheckmark];
  cell.contentView.backgroundColor = [UIColor yellowColor];
  cell.backgroundView.backgroundColor = [UIColor yellowColor];
  return cell;
}

-(void)dealloc {
  [super dealloc];
}
@end

15. Notice that the tableView:cellForRowAtIndexPath: method sets the cell’s accessory, background color, and image.

16. Click Run. The table cells have the customized appearance (Figure 10-21).

Notice in the previous example that you initialize the cell every time it’s called in the table View:CellForRowAtIndexPath: method. This initialization is per Apple’s documentation, Table View Programming Guide for iOS. The “tableView:cellForRowAtIndexPath should always reset all content when reusing a cell.” That is why in the tableView:cellForRowAtIndexPath: you set the cell’s background color, image, and label text. In a more realistic example, you would probably vary a cell’s content depending upon the row.

Image

Figure 10-21 Application with background color

Implementing a UITableViewCell in its own nib does not require implementing a UITableViewCell class, as you did in the example. However, if you wish to override a UITableViewCell’s methods, such as setSelected, you must implement your own custom class. You then set the UITableViewCell’s type in Interface Builder to be your custom subclass. In this example, that class is MyCellView. Implementing a custom UITableViewCell subclass is also a convenient location to place IBOutlets and IBActions for the controls on your custom UITableViewCell control.

NOTE
For more information on table view cells, refer to “A Closer Look at Table-View Cells” in Apple’s “Table View Programming Guide for iOS.”

Using Tables with Navigation Bars and Tabs

Almost every application using a table associates that table with a navigation bar. Moreover, in applications of any complexity, the application also usually organizes its tasks into different tabs. For instance, the iPod application has different tabs for different views of its data. The Artists tab, for instance, shows a user’s multimedia sorted by artist. At the iPod application’s top is a navigation bar. When a user selects an artist row, the navigation controller pushes another view onto the navigation controller and displays it.

In the following Try This, we try to add some real-world credibility to this chapter by having you implement a table in a navigation controller that is in a Tab Bar tab. Although initially confusing, this is such a common user interface pattern that you should definitely understand the next task.

Try This: Using a Table in a Navigation Controller in a Tab

This is a long but useful task. Much of it is repetition from previous chapters, but the combination of a table in a navigation controller in a tab is such a common application pattern that it is worth presenting here in detail, even if much of the task is repetitive.

Creating and Connecting the Views

1. Create a new Window-based Application. Name the application TabNavTable.

2. Add a UITabBarController as an IBOutlet to TabNavTableAppDelegate. Name the outlet myCont (Listing 10-24).

Listing 10-24 TabNavTableAppDelegate.h

#import <UIKit/UIKit.h>
@interface TabNavTableAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
UITabBarController *myCont;
}
@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UITabBarController *myCont;
@end

3. Change TabNavTableAppDelegate’s application:didFinishLaunchingWithOptions method in TabNavTableAppDelegate to load the newly added tab bar controller’s root view (Listing 10-25).

Listing 10-25 TabNavTableAppDelegate.m

#import "TabNavTableAppDelegate.h"
@implementation TabNavTableAppDelegate
@synthesize window;
@synthesize myCont;
- (BOOL)application:(UIApplication *)application
     didFinishLaunchingWithOptions:(NSDictionary *) launchOptions {
 [window addSubview: myCont.view];
 [window makeKeyAndVisible];
}

 -(void)dealloc {
  [myCont release];
  [window release];
  [super dealloc];
}
@end

4. Save your changes and open MainWindow.xib in Interface Builder.

5. Add a tab bar controller from the library to the document window.

6. Delete the tab bar controller’s root view controller (View Controller - Item 1).

7. Add a navigation controller in the previously deleted root view controller’s place (Figure 10-22).

8. Change the navigation controller’s view controller, View Controller (Root View Controller), from a UIViewController to a UITableViewController. For now we’ll leave its type as UITableViewController; then in a few steps, you change it to your own class, MyTableViewController.

9. Connect TabNavTableAppDelegate’s myCont property to the tab bar controller (Figure 10-23).

10. Save your changes.

11. Create a new subclass of UITableViewController. Name the class MyTableViewController and add the table view controller methods in Listings 10-26 and 10-27.

Listing 10-26 MyTableViewController.h

#import <UIKit/UIKit.h>
@interface MyTableViewController : UITableViewController {
 NSMutableArray * tableDataList;
}
@property (nonatomic, retain) NSMutableArray * tableDataList;
@end

Image

Figure 10-22 Navigation controller in place of tab’s view controller

Image

Figure 10-23 Application with a table, navigation bar, and tab bar

Listing 10-27 MyTableViewController.m

#import "MyTableViewController.h"
@implementation MyTableViewController
@synthesize tableDataList;

-(void) viewDidLoad {
NSMutableArray * tempArray = [[[NSMutableArray alloc]
    initWithObjects:@"Item One", @"Item Two", @"Item Three",
        @"Item Four", @"Item Five", @"Item Six",@"Item Seven",
        @"Item Eight", @"Item Nine", @"Item Ten",
        @"Item Eleven", @"Item Twelve", @"Item Thirteen",
        @"Item Fourteen", @"Item Fifteen", @"Item Sixteen",
        @"Item Seventeen",@"Item Eighteen",
        @"Item Nineteen", @"Item Twenty", nil] autorelease];
 self.tableDataList = tempArray;
}

-(NSInteger) tableView : (UITableView *) tableView
    numberOfRowsInSection: (NSInteger) section {
 return [self.tableDataList count];
}

-(UITableViewCell *) tableView : (UITableView *) tableView
    cellForRowAtIndexPath:(NSIndexPath *) indexPath {
 UITableViewCell *cell =
     [tableView dequeueReusableCellWithIdentifier:@"acell"];
 if(cell == nil) {
  cell = [[[UITableViewCell alloc] initWithStyle:
  UITableViewCellStyleDefault reuseIdentifier:@"acell"] autorelease];
 }
 cell.textLabel.text =
    [self.tableDataList objectAtIndex:[indexPath row]];
 return cell;
}

-(void)dealloc {
 [tableDataList release];
 [super dealloc];
}
@end

12. Notice that you’re adding an NSMutableArray named tableDataList and populating it in MyTableViewController’s viewDidLoad method.

13. Open MyTableViewController.xib, which was created along with the class. Notice that it already has a table view, and the dataSource and delegate outlets are already set up for you.

NOTE
In Step 14 you are adding the table directly to the nib as the main view. Contrast this with how you added the table view in this chapter’s first Try This example. Both techniques work.

14. Open MainWindow.xib in Interface Builder.

15. In the document window, expand Tab Bar Controller, and then expand Navigation Controller (Item). Change the Table View Controller (Navigation Item) from a UITableViewController to MyTableViewController. Don’t forget to change its NIB Name in the Attributes Inspector to MyTableViewController to show the table is loaded from another nib.

16. Save and click Run. The first tab contains a navigation bar and a table view (Figure 10-24).

Image

Figure 10-24 Table in a tab bar

Handling Row Selections

1. Add an IBOutlet to MyTableViewController that references a UINavigationController. Name the outlet navCont. Don’t forget to synthesize it and to release it. Save and compile.

2. Open MainWindow.xib. In the editing pane, expand Tab Bar Controller and then Navigation Controller - Item. Connect MyTableViewController’s navCont outlet to the Navigation Controller - Item.

3. Save and click Run. If you completed the steps correctly, there is no change in the application’s appearance.

4. Create a new UIViewController subclass and name it TableViewDetailsViewController (Listings 10-28 and 10-29). Select the check box to also create an accompanying xib for the class. Although you leave this class empty in this task, in a real-world project, this class would contain logic.

Listing 10-28 TableViewDetailsViewController.h

#import <Foundation/Foundation.h>
@interface TableViewDetailsViewController : UIViewController {
}
@end

Listing 10-29 TableViewDetailsViewController.m

#import "TableViewDetailsViewController.h"
@implementation TableViewDetailsViewController
@end

5. Open TableViewDetailsViewController.xib in Interface Builder.

6. Change the view’s background color.

7. Because you created the XIB at the same time as the class, the File’s Owner class is already set to TableViewDetailsViewController and the File’s Owner view outlet is already connected to the view in the document window.

8. Save your changes.

9. Open MyTableViewController and import TableViewDetailsViewController.h.

10. Implement the tableView:didSelectRowAtIndexPath method in MyTableViewController (Listing 10-30).

Listing 10-30 The tableView:didSelectRowAtIndexPath: method added to MyTableViewController

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:
(NSIndexPath *) indexPath {
NSLog(@"pushing...");
TableViewDetailsViewController * temp = [[
[TableViewDetailsViewController alloc] initWithNibName:
@"TableViewDetailsViewController" bundle:nil] autorelease];
[self.navCont pushViewController:temp animated:YES];
}

11. Build and run in iPhone Simulator. Upon clicking a row, you are taken to the details page (Figure 10-25).

Image

Figure 10-25 Clicking the row takes the user to detail view.

Editing Table Cells

Table cells can be edited. You can add new rows, delete existing rows, and reorder rows. The way it works is like this: A user clicks a button that puts the table into edit mode. Edit mode displays insert or delete accessories used for adding and deleting rows. These accessories are displayed on a cell’s left side. Editing mode displays reorder accessories on a table cell’s right side.

Getting to Edit Mode

This chapter ends by discussing how to edit a table. Tables not only display data, they also allow adding rows, deleting rows, and changing the order of rows. A table has two modes: its normal display mode and edit mode. When a table is in edit mode, it can display accessories for inserting, deleting, and rearranging table cells. An application places a table in edit mode by sending a message to the table view’s setEditing:animated: method. For instance, you might call a table’s setEditing:animated: method by implementing an IBAction called by a button on a form.

-(IBAction) edit {
 [self.myTableView setEditing:YES animated:YES];
}

However, a self-created button is not how tables are usually placed into edit mode. Rather than specifically creating an action and manually calling a method, you usually use a navigation item and let it automatically activate a table view’s edit mode. Remember, 90 percent of the time you will implement a table view in a navigation controller. That navigation controller’s navigation bar can contain a right button. One choice you have when creating a navigation bar button is creating an Edit button.

self.navigationItem.rightBarButtonItem = myTableController
.editButtonItem;

When you set that button to a table controller’s editButtonItem, the controller automatically knows to enter edit mode.

Edit Mode Methods

The methods tableView:canEditRowAtIndexPath:, tableView:canMoveRowAtIndexPath:, tableView:commitEditingStyle:forRowAtIndexPath:, and tableView:commitEditingStyle: forRowAtIndexPath: are four methods you should implement in your UITableViewDataSource protocol adoptee.

-(BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:
(NSIndexPath *)indexPath
-(BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:
(NSIndexPath *)indexPath
-(void)tableView:(UITableView *)tableView commitEditingStyle:
(UITableViewCellEditingStyle) editingStyle forRowAtIndexPath:
(NSIndexPath *) indexPath
-(UITableViewCellEditingStyle)tableView:(UITableView *) tableView
editingStyleForRowAtIndexPath:(NSIndexPath *) indexPath

A table knows a row is editable by the tableView:canEditRowAtIndexPath: method. If you wish all rows to be editable, simply have the method return YES; otherwise, implement code to determine if a particular row is editable. If you omit this method, no rows are editable.

The tableView:editingStyleForRowAtIndexPath: method informs the table what style editing accessory the row should have. If this method returns a UITableViewCellEditingStyleNone, no accessory is displayed. If this method returns UITableViewCellEditingStyleDelete, the delete accessory is displayed. And if the method returns UITableViewCellEditingStyleInsert, the insert accessory is displayed. The example code in Listing 10-31 illustrates this.

Listing 10-31 The tableView:editingStyleForRowAtIndexPath method

-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView
     editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
 if(indexPath.row == 0 || indexPath.row == [self.tableDataList count]) {
  return UITableViewCellEditingStyleNone;
 }
 return UITableViewCellEditingStyleDelete;
}

A table knows a table row is movable by the tableView:canMoveRowAtIndexPath: method. Like the tableView:canEditRowAtIndexPath: method, simply have the method return YES if all rows are movable; otherwise, write your own custom code. If you omit this method, no rows are movable.

The tableView:canMoveRowAtIndexPath: method only prevents particular rows from being directly moved by a user. A user can still move another row to the position held by the unmovable row, thus moving the unmovable row indirectly. To prevent this behavior, you can implement the tableView:targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath: method in your table view’s delegate. If a proposed move is acceptable, return the proposedDestinationIndexPath; otherwise, return the sourceIndexPath. The method’s signature follows.

 -(NSIndexPath *)tableView:(UITableView *)tableView
targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath
toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath

The tableView:commitEditingStyle:forRowAtIndexPath: commits a row insertion or deletion. Notice the editingStyle parameter. If a row is deleted, the table view sends a UITableViewCellEditingStyleDelete style as the editingStyle parameter. If a row is inserted, the table view sends a UITableViewCellEditingStyleInsert style. It uses the editingStyle parameter to tell it which action resulted in the method being called.

This method is for implementing code that handles a row insertion or deletion. For instance, in this chapter, data comes from an NSArray or NSMutableArray. When using an NSMutableArray as a data source for a table, deleting a row means you must delete the corresponding item from the array. If inserting, you must add an item at the index where the row was inserted and remove the item from its old location in the array.

Try This: Editing Rows

1. Copy the TabNavTable project to a new location and open it in Xcode.

2. Edit MyTableViewController to add the editing functionality (Listings 10-32 and 10-33)

3. Create a new view controller class called AddItemViewController. Be sure to select the “With XIB for user interface” check box to create an associated xib.

Listing 10-32 MyTableViewController.h

#import <UIKit/UIKit.h>
#import "TableViewDetailsViewController.h"
#import "AddItemViewController.h"
@interface MyTableViewController : UITableViewController {
 NSMutableArray * tableDataList;
UINavigationController * navCont;
AddItemViewController * addItemController;
UIBarButtonItem * addButton;
}
@property (nonatomic, retain) IBOutlet UINavigationController *
navCont;
@property (nonatomic, retain) NSMutableArray * tableDataList;
@property (nonatomic, retain) AddItemViewController *
addItemController;
@property (nonatomic, retain) IBOutlet UIBarButtonItem * addButton;
-(IBAction) exitAndSave: (NSString *) newValue;
-(IBAction) enterAddMode: (id) sender;
@end

Listing 10-33 MyTableViewController.m

Image

Image

Image

4. Add a reference to MyTableViewController in AddItemViewController; however, instead of importing MyTableViewController, use the @class macro (Listings 10-34 and 10-35). Name the property parentTable.

Listing 10-34 AddItemViewController.h

#import <UIKit/UIKit.h>
@class MyTableViewController;
@interface AddItemViewController : UIViewController {
 MyTableViewController * parentTable;
 UITextField * addedName;
 UIBarButtonItem * doneButton;
}
@property (nonatomic, retain) IBOutlet UITextField * addedName;
@property (nonatomic, retain) MyTableViewController * parentTable;
@property (nonatomic, retain) IBOutlet UIBarButtonItem * doneButton;
-(IBAction) exitAndSave: (id) sender;
@end

Listing 10-35 AddItemViewController.m

#import "AddItemViewController.h"
@implementation AddItemViewController
@synthesize addedName;
@synthesize parentTable;
@synthesize doneButton;
@synthesize addedName;

-(void) viewDidLoad {
 self.navigationItem.title = @"Add Item";
 self.navigationItem.rightBarButtonItem = self.doneButton;
}

-(void)dealloc {
 [parentTable release];
 [doneButton release];
 [addedName release];
 [super dealloc];
}

-(IBAction) exitAndSave: (id) sender {
 [self.parentTable exitAndSave:self.addedName.text];
}
@end

5. Add an IBOutlet for a UITextField. Name the text field addedName. Add an IBOutlet for a bar button item. Name the button doneButton. Do not forget to synthesize the outlets/properties. Add an IBAction called exitAndSave (Listings 10-34 and 10-35).

6. Open MyTableViewController and import AddItemViewController. Add a property referencing the AddItemViewController. Name the reference addItemController. Also, add a method named exitAndSave and an IBAction named enterAddMode. Note that exitAndSave takes an NSString as a parameter and is not an IBAction (see Listings 10-32 and 10-33).

7. Add an IBOutlet for a bar button item named addButton (see Listing 10-32).

8. Change the viewDidLoad method so that it sets its navigation item’s rightBarButtonItem to its editButtonItem and its navigation item’s leftBarButtonItem to addButton (see Listing 10-33).

9. Implement the UITableViewDataSource protocol methods: tableView:canEditRowAtIndex path:, tableView:commitEditingStyle:forRowAtIndex:, tableView:moveRowAtIndexPath: toIndexPath:, tableView: editingStyleForRowAtIndexPath:, and tableView:canMoveRowA tIndexPath: (see Listing 10-33).

10. Open AddItemViewController.xib and add a bar button item to the editing pane. Change the newly added bar button’s identifier to Done.

11. Connect the File’s Owner doneButton outlet to the newly created Done button.

12. Add a UITextField to the canvas. Attach it to the File’s Owner addedName outlet.

13. Connect the File’s Owner exitAndSave action to the newly added Done bar button item.

14. Save your changes.

15. Open AddItemViewController and implement the viewDidLoadMethod so that it initializes its navigationItem’s title to “Add Item” and its rightBarButtonItem to its doneButton (Listing 10-35).

16. Open MyTableViewController.xib in Interface Builder and add a bar button item to the document window. Change the button’s identifier to Add.

17. Connect the File’s Owner enterAddMode property to the newly added bar button item. Also connect the File’s Owner addButton to the newly added bar button item.

18. Return to MyTableViewController and implement the exitAndSave and the enterAddMode methods like Listing 10-33.

19. Return to AddItemViewController and implement the exitAndSave method like Listing 10-35.

20. Ensure all your properties are being properly released in each class dealloc method.

21. Save all of your changes and click Run.

When the application starts, it presents the user with the table view. Upon tapping the Edit button, the application presents the table in edit mode (Figure 10-26). If the user clicks one of the table’s delete accessories, the accessory rotates 90 degrees and presents a Delete button (Figure 10-27). Upon clicking Delete, the row is deleted. When a user clicks and drags the move accessory on the right, he or she can move the row to the new location desired (Figure 10-28).

Image

Figure 10-26 The table view in edit mode

Image

Figure 10-27 Deleting a row

Image

Figure 10-28 Moving a row

If a user decides to add a row, he or she taps the Add button, which presents the user with a view to enter the new object’s details (Figure 10-29). If the user decides to cancel this action, he or she simply taps the Back button. If the user decides to save the record, he or she taps the Done button, which saves the record and returns the user to the table with the newly added table row (Figure 10-30).

To support moving rows, you implemented the tableView:moveRowAtIndexPath:toIndex Path: method. In that method, you obtained the object from its old position in the tableDataList, removed it, and then added it to its new location. You also implemented the tableView:canMove RowAtIndexPath: and tableView:canEditRowAtIndexPath: methods. If you needed to only allow certain rows to be editable, you would add that code to the tableView:canEditRowAt IndexPath: method. If you needed to only allow certain rows to be movable, you would add code to the tableView:canMoveRowAtIndexPath: method. Here, both methods simply return YES.

Supporting delete functionality requires a little more code than moving rows, but not much. You implemented the tableView:commitEditingStyle:forRowAtIndexPath: method. In the example, this method removes an object from the index and deletes the row from the table.

Notice you disallowed inserting rows by implementing the tableView:editingStyleForRow AtIndexPath: and having it return UITableViewCellEditingStyleDelete. This method informs a table what accessory, if any, it should display in a row when the table enters edit mode. Here, it displays the delete accessory.

Image

Figure 10-29 Adding an item

Image

Figure 10-30 The table view with the newly added item

You did not implement insert functionality at all. Inserting uses similar logic to deleting a row, only the style is UITableViewCellEditingStyleInsert. You would also implement code to initialize an object and then insert it in the data source at the proper location. For instance, in the previous example, you could change it to support inserting rows if you changed tableView: editingStyleForRowAtIndexPath: to return UITableViewCellEditingStyleInsert and added the following lines to the tableView:commitEditingStyle:forRowAtIndex: method:

else if (editingStyle == UITableViewCellEditingStyleInsert) {
     [self.tableDataList insertObject:@"Uninitialized" atIndex:indexPath.row];
     [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
          withRowAnimation:YES];
}

In this example, however, adding a row does not use a table’s edit mode. Although it is possible to insert a row using a table’s edit mode, it is often not practical. Usually, a table lists items, where each row represents an individual item. Because each item has details associated with it, adding is often accomplished using a separate view controller. Here, you added a left bar button with an addition symbol. When a user clicks the button, it calls MyTableViewController’s enterAddMode method. This method presents the AddItemViewController’s view. Note that AddItemViewController and MyTableViewController both have references to each other. Upon finishing entering the new item, a user clicks Done, and the Done button fires AddItemViewController’s exitAndSave method. This method calls MyTableViewController’s exitAndSave method and the row is added as the table’s last row.

Summary

This was a long and difficult chapter. But the UITableView is arguably iOS’s most difficult, yet most important, view. If you do not understand the UITableView and its associated classes, you do not understand iOS programming. In this chapter, you learned how to use a UITableView and implement a table view’s delegate and data source in a custom class. You also learned how to group a table and how to index it. And you learned how to customize a table cell’s appearance.

After learning how to implement a table and customize it, you then learned a technique for adding a table to a navigation controller contained in a tab. This was a long task, but as it is such a ubiquitous layout pattern, it is a necessary task. After learning this technique, you then learned how to add, move, and delete rows from a table.

NOTE
There are many more methods and properties you can use to customize a table cell’s behavior and appearance than presented in this chapter. For more information, refer to the online class references for each of the classes covered in this chapter and also reference Apple’s “Table View Programming Guide for iOS.”

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

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