devxlogo

More Table View Tricks for the iPhone

More Table View Tricks for the iPhone

y previous article on using the Table View in iPhone discussed the basics of this view and how you can use it to display rows of data and how users can use it to drill into more details. However, you can do many more things with the Table View than what that article could possibly cover. Hence, this follow-up article covers some more uses of the Table View. Specifically, you will learn:

  • How to group the rows in a Table View into sections
  • How to display an index on the right side of the Table View so that users can directly jump to a particular section of the Table View simply by tapping on the index
  • How to add a Search Bar to the Table View so that users can search for rows in the Table View
Author’s Note: For this article, I will extend the project created in the previous article. I suggest you download the project for the previous article if you want to follow the examples illustrated here.

Grouping the Rows in a Table View into Sections

The previous article’s example stored a list of all the US states using an NSMutableArray object and displayed all 50 states in the Table View as a single section. It would be very useful to group the 50 states into sections, perhaps alphabetically. The Table View allows you to group rows into sections, so that users have a visual way of viewing related rows.

To group the states into sections, you first declare another NSMutableArray object named stateIndex in the RootViewController.m file:

#import "RootViewController.h"@implementation RootViewController@synthesize detailsViewController;NSMutableArray *listOfStates;NSMutableArray *stateIndex;

The stateIndex will be used to store the starting character of each state (i.e., “A”, “B”, “C”, etc.).

In the viewDidLoad method, add the following block of code in bold:

- (void)viewDidLoad {    //---initialize---    listOfStates = [[NSMutableArray alloc] init];        //---add items---    [listOfStates addObject:@"ALABAMA"];     [listOfStates addObject:@"ALASKA"]; //...//...    [listOfStates addObject:@"WISCONSIN"];    [listOfStates addObject:@"WYOMING"];        //---create the index---    stateIndex = [[NSMutableArray alloc] init];        for (int i=0; i            self.navigationItem.title = @"States of America";    [super viewDidLoad];}

Rather than manually creating the index by adding “A”, “B”, “C”, etc., to the stateIndex object, you shall iterate through the listOfStates object and then extract the first character of each state. Each character extracted is then added into the stateIndex object. This approach is useful, as you can freely add or remove states from the listOfStates object. At the same time, the approach insures that certain characters (such as “B”) will not be added if there are no state names beginning with that particular character.

To insure that the Table View displays the rows in sections, implement the numberOfSectionsInTableView: method and return the size of the stateIndex object:

//---set the number of sections in the table---- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {return [stateIndex count];}

For each section, you will display a header. In this case, you shall display the beginning character of each state. To display the header for each section, implement the tableView:titleForHeaderInSection: method:

//---set the title for each section---- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {return [stateIndex objectAtIndex:section];}

To set the number of rows in each section, you need to implement the tableView:numberOfRowsInSection: method:

//---set the number of rows in each section---- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {    //---get the letter in each section; e.g., A, B, C, etc.---    NSString *alphabet = [stateIndex objectAtIndex:section];    //---get all states beginning with the letter---    NSPredicate *predicate =         [NSPredicate predicateWithFormat:@"SELF beginswith[c] %@", alphabet];    NSArray *states = [listOfStates filteredArrayUsingPredicate:predicate];        //---return the number of states beginning with the letter---return [states count];    }

Here, you use an NSPredicate object to extract all the states beginning with a particular alphabet:

    //---get all states beginning with the letter---    NSPredicate *predicate =         [NSPredicate predicateWithFormat:@"SELF beginswith[c] %@", alphabet];NSArray *states = [listOfStates filteredArrayUsingPredicate:predicate];
Figure 1. Displaying the 50 States Grouped Alphabetically: This screenshot shows the Table View displaying the states in sections.

The size of the states object indicates the number of states beginning with that particular character, and hence the number of rows for that particular section.

To ensure that the correct state (or states) goes into each section, modify the tableView:cellForRowAtIndexPath: method as follows:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {    static NSString *CellIdentifier = @"Cell";    UITableViewCell *cell =         [tableView dequeueReusableCellWithIdentifier:CellIdentifier];    if (cell == nil) {        cell = [[[UITableViewCell alloc]                    initWithStyle:UITableViewCellStyleDefault                    reuseIdentifier:CellIdentifier] autorelease];}    //---get the letter in the current section---    NSString *alphabet = [stateIndex objectAtIndex:[indexPath section]];    //---get all states beginning with the letter---NSPredicate *predicate =         [NSPredicate predicateWithFormat:@"SELF beginswith[c] %@", alphabet];    NSArray *states = [listOfStates filteredArrayUsingPredicate:predicate];    if ([states count]>0) {    //---extract the relevant state from the states object---        NSString *cellValue = [states objectAtIndex:indexPath.row];        cell.textLabel.text = cellValue;            UIImage *image = [[UIImage imageNamed:@"USA.jpeg"]                           _imageScaledToSize:CGSizeMake(30.0f, 32.0f)                          interpolationQuality:1];        cell.imageView.image = image;        cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;    }    return cell;}

Essentially, you did the same thing as you did in the tableView:numberOfRowsInSection: method. In this case, instead of returning the size of the states object, you extracted the relevant state and used it to populate the Table View cell object. Finally, release the stateIndex object in the dealloc method:

- (void)dealloc {    [self.detailsViewController release];    [listOfStates release];    [stateIndex release];    [super dealloc];}

That’s it! Press Command-R to test the application on the iPhone Simulator. Figure 1 shows the Table View displaying the states in sections.

Displaying an Index

Figure 2. Displaying an Index to the Right Side of the Screen: This screenshot shows the Table View displaying the index.

If you have a large number of rows displayed in the Table View, scrolling through them can be quite laborious. Instead of asking the user to scroll from the top to the bottom, it would be helpful to offer a way to jump directly to a particular section. By using an index, you can enable the Table View to do just that. In this example, you have grouped the various states alphabetically and, hence, it would logical to use the alphabet as the index.

The Table View displays the index on the right side of the screen. The user employs the index, which very useful for large number of rows, to jump directly to a section.

Displaying the index in the Table View is very easy: just implement the sectionIndexTitlesForTableView: method and return an array containing the index:

//---set the index for the table---- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {    return stateIndex;}

In this case, you simply return the stateIndex object.Press Command-R to test the application on the iPhone Simulator. Figure 2 shows the Table View displaying the index.

Adding Search Capabilities

Besides the ability to use an index to jump directly to a section, another very popular feature commonly found in the Table View is search. Using the Search Bar located at the top of the Table View, users can type a search keyword and the Table View dynamically shows the list of matching rows.

Let’s see how you can add the search functionality to the Table View.

In the RootViewController.h file, add the following lines of code in bold:

#import "DetailsViewController.h"@interface RootViewController : UITableViewController {    DetailsViewController *detailsViewController;        IBOutlet UISearchBar *searchBar;    BOOL isSearchOn;    BOOL canSelectRow;        NSMutableArray *searchResult;}@property (nonatomic, retain) DetailsViewController *detailsViewController;@property (nonatomic, retain) UISearchBar *searchBar; - (void) doneSearching: (id)sender;- (void) searchTableView;@end

You added an IBOutlet named searchBar so that you can connect to a Search Bar view in Interface Builder. You also added two Boolean variables and an NSMutableArray object to temporarily store the result of the search. In addition, you declared two methods, both which will be defined in the RootViewController.m file later.

Double-click on the RootViewController.xib file to edit it in Interface Builder. In the Table View window, add a Search Bar view (from the Library). Right-click on the Search Bar and connect the delegate to the File’s Owner item (see Figure 3).

Control-click the File’s Owner item and drag and drop it onto the Search Bar view. Select searchBar. If you right-click on the Search Bar view, you should see the connections as shown in Figure 4.


Figure 3. Connecting the Delegate Outlet to the File’s Owner Item: Right-click on the Search Bar and connect the delegate to the File’s Owner item.
 
Figure 4. The Connections Made for the Search Bar View: If you right-click on the Search Bar view, you should see these connections.

In the RootViewController.m file, first add in the following statement to create the getters and setters for the searchBar property:

#import "RootViewController.h"@implementation RootViewController@synthesize detailsViewController;@synthesize searchBar;

In the viewDidLoad method, add the following lines of code in bold:

- (void)viewDidLoad {    //---initialize---    listOfStates = [[NSMutableArray alloc] init];        //---add items---    [listOfStates addObject:@"ALABAMA"]; [listOfStates addObject:@"ALASKA"]; //...    //...    [listOfStates addObject:@"WISCONSIN"];    [listOfStates addObject:@"WYOMING"];            //---create the index---    stateIndex = [[NSMutableArray alloc] init];        for (int i=0; i//---set the correction type for the search bar---    searchBar.autocorrectionType = UITextAutocorrectionTypeYes;        //---used for storing the search result---    searchResult = [[NSMutableArray alloc] init];        isSearchOn = NO;    canSelectRow = YES;        self.navigationItem.title = @"States of America";        [super viewDidLoad];}

Implement the searchBarTextDidBeginEditing: method as shown below. This method will be called when the user taps on the Search Bar.

//---fired when the user taps on the Search Bar---- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {    if ([searchResult count] > 0) {        canSelectRow = YES;        self.tableView.scrollEnabled = YES;    } else {        canSelectRow = NO;        self.tableView.scrollEnabled = NO;    }    isSearchOn = YES;        //---add the Done button at the top---self.navigationItem.rightBarButtonItem =     [[[UIBarButtonItem alloc]        initWithBarButtonSystemItem:UIBarButtonSystemItemDone            target:self action:@selector(doneSearching:)] autorelease];}

When the user taps on the Search Bar, the keyboard will appear. In this searchBarTextDidBeginEditing: method, you first check to see if the temporary search result (searchResult) has any items in it. If it returns at least one item, the user will be able to scroll through the Table View. In addition, you add a Done button to the top right corner of the Navigation bar (see Figure 5). The Done button will invoke the doneSearching: method when the user taps it.

Figure 5. Displaying a Done Button to the Right of the Navigation Bar: You add a Done button to the top right corner of the Navigation bar.

Define the doneSearching: method as follows:

//---done with the searching---- (void) doneSearching:(id)sender {    searchBar.text = @"";    isSearchOn = NO;    canSelectRow = YES;        self.tableView.scrollEnabled = YES;    self.navigationItem.rightBarButtonItem = nil;        //---hides the keyboard---    [searchBar resignFirstResponder];    //---refresh the Table View---    [self.tableView reloadData];}

The doneSearching: method will clear the text in the Search Bar view and then hide the keyboard. It will also reload the Table View (via the reloadData method of the Table View) with the original set of 50 states. Next, implement the searchBar:textDidChange: method as shown below:

//---fired when the user types something into the Search Bar---- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {    [searchResult removeAllObjects];    //---if there is something to search for---    if ([searchText length] > 0) {        isSearchOn = YES;        canSelectRow = YES;        self.tableView.scrollEnabled = YES;        [self searchTableView];    }    else {                //---nothing to search---        isSearchOn = NO;        canSelectRow = NO;        self.tableView.scrollEnabled = NO;    }    [self.tableView reloadData];}

The searchBar:textDidChange: method will be fired whenever the user types something into the Search Bar view. As the user types, the Search Bar view continually calls the searchTableView method, which you will define next. The searchTableView method iterates through the listOfStates object and looks for all states containing the search string. All matching states are stored in the searchResult object.

//---performs the searching using the array of states---- (void) searchTableView {    //---clears the search result---    [searchResult removeAllObjects];        for (NSString *str in listOfStates)    {        NSRange titleResultsRange =             [str rangeOfString:searchBar.text options:NSCaseInsensitiveSearch];        if (titleResultsRange.length > 0)            [searchResult addObject:str];    }}

Implement the searchBarSearchButtonClicked: method as shown below, which will be called when the user taps on the Search button on the keyboard:

//---fired when the user taps the Search button on the keyboard---- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {    [self searchTableView];}

Implement the tableView:willSelectRowAtIndexPath: method as shown below, which will be called just when the user selects a row:

//---fired before a row is selected---- (NSIndexPath *)tableView :(UITableView *)theTableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {    if (canSelectRow)        return indexPath;    else        return nil;}

Modify the numberOfSectionsInTableView: method as shown below so that it will show a single section in the Table View when the user is performing the search:

//---set the number of sections in the table---- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {    if (isSearchOn)        return 1;    else        return [stateIndex count];}

Modify the tableView:numberOfRowsInSection: method so that when the user is performing the search, the number of rows in the section is the number of items in the searchResult object:

//---set the number of rows in each section---- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {    if (isSearchOn) {        return [searchResult count];    } else {                //---get the letter in each section; e.g., A, B, C, etc.---        NSString *alphabet = [stateIndex objectAtIndex:section];                //---get all states beginning with the letter---        NSPredicate *predicate =             [NSPredicate predicateWithFormat:@"SELF beginswith[c] %@", alphabet];        NSArray *states = [listOfStates filteredArrayUsingPredicate:predicate];                //---return the number of states beginning with the letter---        return [states count];    }}

Also, modify the tableView:titleForHeaderInSection: and sectionIndexTitlesForTableView: methods as follows. When performing the search, there is no need to display a title for the section nor to display the index.

//---set the title for each section---- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {    if (isSearchOn)        return nil;    else        return [stateIndex objectAtIndex:section];}//---set the index for the table---- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {    if (isSearchOn)        return nil;    else        return stateIndex;}

In the tableView:cellForRowAtIndexPath: method, modify the code shown in bold below so that when performing a search, you will display the states saved in the searchResult object. When not searching, you will display the states in different sections, categorized by their beginning characters:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {    static NSString *CellIdentifier = @"Cell";    UITableViewCell *cell =     [tableView dequeueReusableCellWithIdentifier:CellIdentifier];    if (cell == nil) {        cell = [[[UITableViewCell alloc]                    initWithStyle:UITableViewCellStyleDefault                    reuseIdentifier:CellIdentifier] autorelease];    }        if (isSearchOn) {                NSString *cellValue = [searchResult objectAtIndex:indexPath.row];        cell.textLabel.text = cellValue;                UIImage *image = [[UIImage imageNamed:@"USA.jpeg"]                           _imageScaledToSize:CGSizeMake(30.0f, 32.0f)                          interpolationQuality:1];        cell.imageView.image = image;                cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;                    } else {        //---get the letter in the current section---        id alphabet = [stateIndex objectAtIndex:[indexPath section]];                //---get all states beginning with the letter---        NSPredicate *predicate =             [NSPredicate predicateWithFormat:@"SELF beginswith[c] %@", alphabet];        NSArray *states = [listOfStates filteredArrayUsingPredicate:predicate];                if ([states count]>0) {            NSString *cellValue = [states objectAtIndex:indexPath.row];            cell.textLabel.text = cellValue;                        UIImage *image = [[UIImage imageNamed:@"USA.jpeg"]                               _imageScaledToSize:CGSizeMake(30.0f, 32.0f)                              interpolationQuality:1];            cell.imageView.image = image;                        cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;        }            }    return cell;}

You also need to modify the tableView:didSelectRowAtIndexPath: method so that you will display the appropriate states selected when the user performs a search:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {        NSString *stateSelected;        if (isSearchOn) {                stateSelected = [searchResult objectAtIndex:indexPath.row];    } else {        stateSelected = [listOfStates objectAtIndex:[indexPath row]];    }    NSString *msg = [[NSString alloc]     initWithFormat:@"You have selected %@", stateSelected];        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"State selected"                                                     message:msg                                                    delegate:self                                           cancelButtonTitle: @"OK"                                           otherButtonTitles:nil];        [alert show];         [alert release];        [msg release];    }

Likewise, you need to modify the tableView:accessoryButtonTappedForRowWithIndexPath: method so that the appropriate state is selected when the user taps the disclosure button:

- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {    NSString *stateSelected;    if (isSearchOn) {                stateSelected = [searchResult objectAtIndex:indexPath.row];    } else {        stateSelected = [listOfStates objectAtIndex:[indexPath row]];    }    NSString *msg =     [[NSString alloc] initWithFormat:@"You have selected %@", stateSelected];        //---Navigate to the details view---    if (self.detailsViewController == nil)    {                DetailsViewController *d =             [[DetailsViewController alloc]              initWithNibName:@"DetailsViewController" bundle:[NSBundle mainBundle]];                self.detailsViewController = d;        [d release];    }            //---set the state selected in the method of the     // DetailsViewController---//    [self.detailsViewController initWithTextSelected:msg];[self.navigationController pushViewController:self.detailsViewController      animated:YES];        [msg release];    }

Finally, release the searchBar property in the dealloc method:

- (void)dealloc {    [self.detailsViewController release];    [listOfStates release];[stateIndex release];    [searchBar release];    [super dealloc];}

That’s it! Press Command-R to test the application on the iPhone Simulator again. Tap on the Search Bar view to start the search. Observe that as you type in your search string, the Table View will display all the rows matching your search string (see Figure 6). When you are done with the search, tap on the Done button and the Table View will display the original list of 50 states.

Changing the Style of the Table View

So far, the Table View has been showing rows in plain format (i.e., rows of data separated by horizontal lines). Besides this style, the Table View also allows you to display sections of rows in groups, visually.

In Interface Builder, edit the RootViewController.xib file, select the Table View, and open the Attributes Inspector window. In the Style property (see Figure 7), select Grouped. Save the file.

When you press Command-R to test the application again, you will notice that all the rows are displayed in groups (see Figure 8).


Figure 6. Searching the Table View: The Table View will display all the rows matching your search string.
 
Figure 7. Modifying the Style of the Table View: In the Style property, select Grouped.
 
Figure 8. Displaying the Rows in a Table View in Visual Groups: When you test the application, you will notice that all the rows are displayed in groups.

What Have You Learned?

In this article, you have seen how to group rows of data in a Table View into sections and then add an index to the right side of the screen so that users can directly jump to a particular section in the table. In addition, you also learned how to implement search functionality in a Table View, a feature that is very useful for large number of rows. Hopefully, you have found this Table View series useful.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist