Bookmark and Share

iPhone SDK Tutorial
Chapter 8. Navigation Controllers






8.3 Checklist - Second Subcontroller

In this section, we're going to implement another able view using accessory icon to let the user select one and only one item from the list. The accessory icon will be used to place a checkmark next to the selected row. When the user touches another row, we'll change the selection.

Because this table view has not detail view, we don't need a new nib. But we need to create another subclass of SecondLevelViewController. Let's make a "CheckListController.m" with Cocoa Touch and Objective_C class and NSObject for Subclass of.

We're going to need a way to keep track of which row is currently selected. So, we'll declare an NSIndexPath property to track the last row selected.

Here is interface file, "CheckListController.h".

#import <UIKit/UIKit>
#import "SecondLevelViewController.h"

@interface CheckListController : SecondLevelViewController {
    NSArray *list;
    NSIndexPath    * lastIndexPath;
}
@property (nonatomic, retain) NSIndexPath * lastIndexPath;
@property (nonatomic, retain) NSArray *list;
@end

Implementation file, "CheckListController.m".

#import "CheckListController.h"

@implementation CheckListController
@synthesize list;
@synthesize lastIndexPath;

- (void)viewDidLoad {
    
    NSArray *array = [[NSArray alloc] initWithObjects:					  
					  @"Cristiano Ronaldo",@"Lionel Messi", @"Fernando Torres", 
					  @"Steven Gerrard", @"Frank Lampard", @"Wesley Sneijder",
                      @"Wayne Rooney", @"Kaka", @"Xavi Hernandez",@"Gianluigi Buffon",
                      @"Didler Drogba", @"Franck Ribery", @"Michael Essien", @"Samuel Eto'o", 
                      @"Luis Fabiano", @"Andres Iniesta",nil];
    self.list = array;
    [array release];
    
    [super viewDidLoad];
}

- (void)viewDidUnload {
    self.list = nil;
    self.lastIndexPath = nil;
    [super viewDidUnload];
}

- (void)dealloc {
    [list release];
    [lastIndexPath release];
    [super dealloc];
}

#pragma mark -

#pragma mark Table Data Source Methods
- (NSInteger)tableView:(UITableView *)tableView 
 numberOfRowsInSection:(NSInteger)section {
    return [list count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView 
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    static NSString *CheckMarkCellIdentifier = @"CheckMarkCellIdentifier";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:
                             CheckMarkCellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault 
                            reuseIdentifier:CheckMarkCellIdentifier] autorelease];
    }
    NSUInteger row = [indexPath row];
    NSUInteger oldRow = [lastIndexPath row];
    cell.textLabel.text = [list objectAtIndex:row];
    cell.accessoryType = (row == oldRow && lastIndexPath != nil) ? 
    UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
    
    return cell;
}

#pragma mark -
#pragma mark Table Delegate Methods

- (void)tableView:(UITableView *)tableView 
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    
    int newRow = [indexPath row];
    int oldRow = (lastIndexPath != nil) ? [lastIndexPath row] : -1;
    
    if (newRow != oldRow)
    {
        UITableViewCell *newCell = [tableView cellForRowAtIndexPath:
                                    indexPath];
        newCell.accessoryType = UITableViewCellAccessoryCheckmark;
        
        UITableViewCell *oldCell = [tableView cellForRowAtIndexPath: 
                                    lastIndexPath]; 
        oldCell.accessoryType = UITableViewCellAccessoryNone;
        lastIndexPath = indexPath;
    }
    
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}
@end

In the tableView: cellForRowAtIndexPath: method, we extract the row from this cell and from the current selection:

    NSUInteger row = [indexPath row];
    NSUInteger oldRow = [lastIndexPath row];

We grab the value for this row from our array and assign it to the cell's title:

    cell.textLabel.text = [list objectAtIndex:row];

Then, we set the accessory to show either checkmark or nothing, depending on whether the two rows are the same. If the rows the table is requesting is the currently selected row, we set the accessory icon to be a checkmark, otherwise, we set it to be nothing.

In tableView: didSelectRowAtIndexPath: method, we grab not only the row that was just selected but also the row that was previously selected.

    int newRow = [indexPath row];
    int oldRow = (lastIndexPath != nil) ? [lastIndexPath row] : -1;

If the new row is not the same as the old one, we grab the cell that was just selected and assign a checkmark for it's icon:

        UITableViewCell *newCell = [tableView cellForRowAtIndexPath:
                                    indexPath];
        newCell.accessoryType = UITableViewCellAccessoryCheckmark;

Then, we grab the previously selected cell, and we set its accessory icon to none:

        UITableViewCell *oldCell = [tableView cellForRowAtIndexPath: 
                                    lastIndexPath]; 
        oldCell.accessoryType = UITableViewCellAccessoryNone;

After that, we store the index path that was just selected into lastIndexPath. So, we'll have it next time a row is selected:

        lastIndexPath = indexPath;

When everything's done, we tell the table view to deselect the row that was just selected. It's because we don't want the row to stay highlighted. We've already marked the row with a checkmark leaving it blue would not looking good. It's a distraction at best.

Next step, we just need to add an instance of this controller to FirstLevelViewController's controllers array. We are doing it by adding the following code to the viewDidLoadMethod in FirstLevelViewController.m:

- (void)viewDidLoad {
    self.title = @"First Level";
    NSMutableArray *array = [[NSMutableArray alloc] init];
	
    // Disclosure Button
    DisclosureButtonController *disclosureButtonController =
    [[DisclosureButtonController alloc] 
     initWithStyle:UITableViewStylePlain];
    disclosureButtonController.title = @"Disclosure Buttons";
    disclosureButtonController.rowImage = [UIImage 
                                imageNamed:@"disclosureButtonControllerIcon.png"];
    [array addObject:disclosureButtonController];
    [disclosureButtonController release];  
	
    // Check List 
    CheckListController *checkListController = [[CheckListController alloc]
                                initWithStyle:UITableViewStylePlain];
    checkListController.title = @"Check One";
    checkListController.rowImage = [UIImage imageNamed:
                                @"checkmarkControllerIcon.png"];
    [array addObject:checkListController];
    [checkListController release];
	
    self.controllers = array;
    [array release];
    [super viewDidLoad];
}

The last line of code is to add the the following header to the file, "FirstLevelViewController.m".

#import "CheckListController.h"


Build, Run, and Play.

CheckListResultA CheckListResultB


8.4 Table Rows Control - Third Subcontroller

So far, we learned how we can add subviews to a table view cell. It's one way of customizing the table view cell. But we did not put anything dynamic controls into the cell but the static labels. Now it's time to add active controls to a table view cell. In this section, it will be a button. Button to each row. One more thing. This time we'll add the control to the accessory pane so that when the user taps the accessory pane it will tap the button.

Let make a new class file, and name it "RowControlsController.m" with Cocoa Touch Class, Objective_C class and NSObject for Subclass of.

First, interface file, "RowControlsController.h"

#import <UIKit/UIKit.h>
#import "SecondLevelViewController.h"

@interface RowControlsController : SecondLevelViewController {
    NSArray *list;
}
@property (nonatomic, retain) NSArray *list;
- (IBAction)buttonTapped:(id)sender;
@end

Here is the implementation file, "RowControlsController.m"

#import "RowControlsController.h"

@implementation RowControlsController
@synthesize list;
- (IBAction)buttonTapped:(id)sender
{
    UIButton *senderButton = (UIButton *)sender;
    UITableViewCell *buttonCell = (UITableViewCell *)[senderButton superview];
    NSUInteger buttonRow = [[self.tableView indexPathForCell:buttonCell] row];
    NSString *buttonTitle = [list objectAtIndex:buttonRow];
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"You tapped the button" 
                                message:[NSString stringWithFormat:@"You tapped the button for %@", buttonTitle] 
                                delegate:nil 
                                cancelButtonTitle:@"OK"
                                otherButtonTitles:nil];
    [alert show];
    [alert release];
}

- (void)viewDidLoad {
    NSArray *array = [[NSArray alloc] initWithObjects:
					  @"RIM",@"iPhone OS", @"Android", @"Windows Mobile", 
					  @"Symbian",@"Palm WebOS", @"BlackBerry OS", @"Maemo", 
                      @"Samsung bada", nil];
    self.list = array;
    [array release];
    [super viewDidLoad];
} 

- (void)viewDidUnload {
    self.list = nil;
    [super viewDidUnload];
}

- (void)dealloc {
    [list release];
    [super dealloc];
}

#pragma mark -
#pragma mark Table Data Source Methods

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

- (UITableViewCell *)tableView:(UITableView *)tableView 
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    static NSString *ControlRowIdentifier = @"ControlRowIdentifier";
    
    UITableViewCell *cell = [tableView 
                             dequeueReusableCellWithIdentifier:ControlRowIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault 
                                       reuseIdentifier:ControlRowIdentifier] autorelease];
        UIImage *buttonUpImage = [UIImage imageNamed:@"button_up.png"];
        UIImage *buttonDownImage = [UIImage imageNamed:@"button_down.png"];
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.frame = CGRectMake(0.0, 0.0, buttonUpImage.size.width, buttonUpImage.size.height);
        [button setBackgroundImage:buttonUpImage forState:UIControlStateNormal];
        [button setBackgroundImage:buttonDownImage forState:UIControlStateHighlighted];
        [button setTitle:@"Tap" forState:UIControlStateNormal];
        [button addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
        cell.accessoryView = button;
        
    }
    NSUInteger row = [indexPath row];
    NSString *rowTitle = [list objectAtIndex:row];
    cell.textLabel.text = rowTitle;
    
    return cell;
} 

#pragma mark -
#pragma mark Table Delegate Methods

- (void)tableView:(UITableView *)tableView 
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    NSUInteger row = [indexPath row];
    NSString *rowTitle = [list objectAtIndex:row];
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"You tapped the row."
                                message:[NSString stringWithFormat:@"You tapped %@.", rowTitle]
                                delegate:nil 
                                cancelButtonTitle:@"OK" 
                                otherButtonTitles:nil];
    [alert show];
    [alert release];
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
} 
@end

In the buttonTapped: method, we are declaring a new UIButton instance and set it to sender.

    UIButton *senderButton = (UIButton *)sender;

Then, we get the button's superview, which is the table view cell for the row it's in, and we use that to determine the row that was pressed and to retrieve the title for that row:

    UITableViewCell *buttonCell = (UITableViewCell *)[senderButton superview];
    NSUInteger buttonRow = [[self.tableView indexPathForCell:buttonCell] row];
    NSString *buttonTitle = [list objectAtIndex:buttonRow];

After that, we show an alert to tell the user that they pressed button:

    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"You tapped the row."
                                message:[NSString stringWithFormat:@"You tapped %@.", rowTitle]
                                delegate:nil 
                                cancelButtonTitle:@"OK" 
                                otherButtonTitles:nil];
    [alert show];
    [alert release];

The method, tableView: cellForRowAtIndexPath:, is setting up the table view cell with the button. We declare an identifier and then use it to request a reusable cell:

    static NSString *ControlRowIdentifier = @"ControlRowIdentifier";
    
    UITableViewCell *cell = [tableView 
                    dequeueReusableCellWithIdentifier:ControlRowIdentifier];

When there are no reusable cells, we create one:

    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault 
                                       reuseIdentifier:ControlRowIdentifier] autorelease];

In order to create the button, we will load in two of the images. One image represents the button in the normal state, the other image represents the button in its highlighted state which indicates the button is being tapped:

    UIImage *buttonUpImage = [UIImage imageNamed:@"button_up.png"];
    UIImage *buttonDownImage = [UIImage imageNamed:@"button_down.png"];

Then, we create a button. Because the buttonType property of UIButton is declared read only, we should create the button using the factory method buttonWithType:. We can't create it using alloc and init, because we wouldn't be able to change the button's type to UIButtonTypeCustom, which we need to do in order to use the custom button images:

    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];

After that, we set the button's size to match the images, assign the images for the two states, and give the button a title:

    button.frame = CGRectMake(0.0, 0.0, buttonUpImage.size.width, buttonUpImage.size.height);
    [button setBackgroundImage:buttonUpImage forState:UIControlStateNormal];
    [button setBackgroundImage:buttonDownImage forState:UIControlStateHighlighted];
    [button setTitle:@"Tap" forState:UIControlStateNormal];

Then, we tell the button to call our action method on the Touch Up Inside event and assign it to the cell's accessory view:

    [button addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
    cell.accessoryView = button;

The method, ableView: didSelectRowAtIndexPath:, is the delegate method that is called after the user selects a row. We are trying to find which row was selected and get the appropriate title from our array:

    NSUInteger row = [indexPath row];
    NSString *rowTitle = [list objectAtIndex:row];

Next, we create another alert to inform the user that they tapped the row, but not the button:

    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"You tapped the row."
                                message:[NSString stringWithFormat:@"You tapped %@.", rowTitle]
                                delegate:nil 
                                cancelButtonTitle:@"OK" 
                                otherButtonTitles:nil];
    [alert show];
    [alert release];
    [tableView deselectRowAtIndexPath:indexPath animated:YES];

Finally, one thing we should do here is adding this controller to the array in FirstLevelViewController. So, modified viewDidLoad method in "FirstLevelViewController.m" is:

#import "FirstLevelViewController.h"
#import "SecondLevelViewController.h"
#import "DisclosureButtonController.h"
#import "RowControlsController.h"
#import "CheckListController.h"

@implementation FirstLevelViewController
@synthesize controllers;
- (void)viewDidLoad {
    self.title = @"First Level";
    NSMutableArray *array = [[NSMutableArray alloc] init];
	
    // Disclosure Button
    DisclosureButtonController *disclosureButtonController =
    [[DisclosureButtonController alloc] 
     initWithStyle:UITableViewStylePlain];
    disclosureButtonController.title = @"Disclosure Buttons";
    disclosureButtonController.rowImage = [UIImage 
                            imageNamed:@"disclosureButtonControllerIcon.png"];
    [array addObject:disclosureButtonController];
    [disclosureButtonController release];  
	
    // Check List 
    CheckListController *checkListController = [[CheckListController alloc]
                            initWithStyle:UITableViewStylePlain];
    checkListController.title = @"Check One";
    checkListController.rowImage = [UIImage imageNamed:
                            @"checkmarkControllerIcon.png"];
    [array addObject:checkListController];
    [checkListController release];

	// Table Row Controls
    RowControlsController *rowControlsController = 
    [[RowControlsController alloc] 
     initWithStyle:UITableViewStylePlain];
    rowControlsController.title = @"Row Controls";
    rowControlsController.rowImage = [UIImage imageNamed:
                                @"rowControlsIcon.png"];
    [array addObject:rowControlsController];
    [rowControlsController release];
	
    self.controllers = array;
    [array release];
    [super viewDidLoad];
}

Notice that we imported a new header to the file.

#import "RowControlsController.h"

Build, Run, and Play.


RowControlsResultA RowControlsResultB



Previous Sections.


Next Sections.