Indexed List in iOS applications

Swarnendu De November 22, 2013

In iOS apps, often we need to implement Indexed List for enabling Index Scrolling in a TableView, just like the contacts view of an iPhone. Indexed List enables fast scrolling to required section without trolling through each and every sections. Since it is required in various apps , it has to be made generic, so that it can be used anywhere required. Just by adding the statements in your model class will work the whole thing out. After this tutorial , your simulator will show output , presented in the following demo.

 

 

Prerequisites:

A basic idea of UITableView and it’s delegate functions are required before begining this project. For more information on UITableView please refer to Apple’s Developer Class Reference for UITableView.

Getting started:

  • First open up your Xcode 5.0  IDE and Select “Create a New Xcode Project”.
  • Under iOS and then Application select Single View Application.
  • Give Product Name “JumpTable”  and Class Prefix “Table”.
  • Select the files TableViewController.h and TableViewController.m , right click  and then select New Group From Selection. Name the group as Controller.
  • Now go to the navigator and right click on any file and select New File . Select Objective C class, name it as “Statements” and make it a subclass of NSObject. Select the files Statements.h and Statements.m , right click  and then select New Group From Selection. Name the group as Model.

At this point your project structure should look like this.

navigatordesign

Go to navigator and click on Statements.h and add the following lines of codes :

@property (strong , nonatomic) NSMutableArray *statement;  //an array to store the statements

-(NSMutableArray *)initializeStatements;  //function to initialize statements

Now we will initialize the above  statement array with a list of statements to be displayed on tableview. Synthesize the above array in Statements.m by adding the following code just after  @implementation Statements.

@synthesize statement;

Add the following function after  above code  to initialize  the aforesaid array.

-(NSMutableArray *)initializeStatements
{
    statement = [[NSMutableArray alloc]initWithCapacity:0];
    [statement addObject:@"your statements"];

    //likewise add objects in array. For better checking purpose, remember to have variance among statements while adding them in array.

    return statement;
}

You can get the statements from here.

Now, go to Controller section and delete the files TableViewController.h and TableViewController.m (since they were subclass of UIViewController instead of UITableviewController) and add in same section create new file with same name but with subclass of  UITableViewController.

Now go to your Main.storyboard and delete the ViewController over there. Go to the Utilities section and drag UITableViewController to your storyboard.

afterDelete

After that, under utilities section, go to the attributes inspector and set the custom class as TableViewController.

Add following the following line just below #import “TableViewController.h” 

#import "Statements.h"

Now, click on the TableViewController.m file and declare the following private property.

@property (strong , nonatomic) NSMutableArray *statementArray;

an synthesize it using

@synthesize statementArray;

This array will contain all the statements to be displayed on the table. Under  viewDidLoad  add following statements after [super viewDidLoad]

statementArray = [[NSMutableArray alloc]initWithCapacity:0];
statements *Object=[[Statements alloc]init];
statementArray=[Object initializeStatements];  //will initialize array with statements
[statementArray sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)];  // will sort the array in ascending order
[self createAlphabetArray];

The following lines of codes includes allocating an array, creating an instance of  class Statements and initializing the allocated array with statements declared within  Statements.h  file. The second last line of code will arrange the statements in statementsArray automatically in ascending order . The last line of code calls a method for creating an array of alphabets, composed by starting letters of statements. Now,  we will implement the method  createAlphabetArray. Add following lines after  viewDidLoad 

-(void)createAlphabetArray
{
    alphabetArray = [[NSMutableArray alloc]initWithCapacity:0];
    for (int i=0; i< statementArray.count; i++)
    {
        NSString *firstletter=[[statementArray objectAtIndex:i]substringToIndex:1];  //modifying the statement to first letter 
        if (![alphabetArray containsObject:firstletter])  //checking the array if the modified statement already exists in array
        {
            [alphabetArray addObject:firstletter];
        }
    }
    [alphabetArray sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)];   //sorting array in ascending array
}

This method will create an array of alphabets , alphabets being taken from first letter of statements and sorted in ascending order. Now we will implement the methods under  #pragma mark – Table view data source .

Under the function  -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView remove all codes and add the following line of code for setting the total number of sections displayed in table.

return alphabetArray.count;

Now, in function  -(NSString*)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section  add the following code to set title for each section.

NSString *title;
for (int i=0; i<alphabetArray.count; i++)
{
   if (section==i)
    {
       title= [alphabetArray objectAtIndex:i];
    }
}
return title;

Add the following function which will take a section as input and return an Array of row contents.

-(NSMutableArray *)getArrayOfRowsForSection:(NSInteger)section
{
    NSString *rowTitle;
    NSString *sectionTitle;
    NSMutableArray *rowContainer=[[NSMutableArray alloc]initWithCapacity:0];

    for (int i=0; i<alphabetArray.count; i++)
    {
        if (section==i)   // check for right section
        {
            sectionTitle= [alphabetArray objectAtIndex:i];  //getting section title
            for (NSString *title in statementArray)
            {
                rowTitle=[title substringToIndex:1];  //modifying the statement to its first alphabet
                if ([rowTitle isEqualToString:sectionTitle])  //checking if modified statement is same as section title
                {
                    [rowContainer addObject:title];  //adding the row contents of a particular section in array
                }
            }
        }
    }
    return rowContainer;
}

Now add the following lines of codes to function  – (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section  for setting number of rows for each section.

NSMutableArray* rowArray=[[NSMutableArray alloc]initWithCapacity:0];
rowArray=[self getArrayOfRowsForSection:section];
return rowArray.count;

The only thing left before drawing each cell is setting the title  for each row. This will be done under the function  -(NSString *)titleForRow:(NSIndexPath *)indexpath  which will take a NSIndexPath object as input and return a title as string. Add the following lines of codes to the above function.

NSMutableArray* rowArray=[[NSMutableArray alloc]initWithCapacity:0];
rowArray=[self getArrayOfRowsForSection:indexpath.section];
NSString *titleToBeDisplayed=[rowArray objectAtIndex:indexpath.row];
return titleToBeDisplayed;

Now, the last only necessary implementation for creating a tableview is implementation of   -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath  function. Add following lines of codes in the above delegate function after  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];  and before  return cell  part.

cell.textLabel.text= [self titleForRow:indexPath];  //getting cell content

After completing this go to your Main.storyboard and then click on the cell appearing on TableViewController . Then set the Identifier with name “Cell” in the attributes inspector under utilities section. Now run your project . Your output will look like this.

scrollTable

The output is somewhat not as desired as we can see the statement but not the full statement. For making the whole sentence visible ad these two lines of codes in the function  – (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath  before  return cell  part.

cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;  //enabling WordWrapping of text in cell
cell.textLabel.numberOfLines = 0;

After adding, run your Project .Even though, all statements are completely visible , you might have observed some places where table contents become messy (where statements are composed of 2 or 3 lines).

Wrong1

This is because no padding have been provided between contents and cell border where contents become large i.e. of 2-3 lines. So, to provide some extra padding we will implement function  -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath  

Add following codes to above function.

NSMutableArray* rowArray=[[NSMutableArray alloc]initWithCapacity:0];
rowArray=[self getArrayOfRowsForSection:indexPath.section];

NSString *cellText =[rowArray objectAtIndex:indexPath.row];  //get row content of cell 
UIFont *cellFont = [UIFont fontWithName:@"Helvetica" size:12.0]; // use fontName and fontSize same as that you used in storyboard 
CGSize constraintSize = CGSizeMake(280.0f, MAXFLOAT);   

//will calculate labelSize for each cell required according to content
CGSize labelSize = [cellText boundingRectWithSize:constraintSize
                                         options:NSStringDrawingUsesLineFragmentOrigin
                                      attributes:@{NSFontAttributeName:cellFont}
                                         context:nil].size;                           

/*
In case you are using Xcode 4.0 use function 
CGSize labelSize = [cellText sizeWithFont:cellFont constrainedToSize:constraintSize lineBreakMode:NSLineBreakByWordWrapping];
*/

CGFloat newHeight;
if (labelSize.height+20 > 40)    // providing a padding of 10 below and 10 above
{
    newHeight= labelSize.height+20;
}
else
{
    newHeight= 40;
}
return newHeight;

Now run your project . iOS simulator will look somewhat like this, which looks preferably neat and proper.

correct

Now, let’s start implementing indexed scrolling in tableview.

For this , we are going to implement two functions. The very first function is  – (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView. This function will return the alphabets that are going to be present in the index scroller, i.e. the alphabetArray.

Add the following line in the above function.

return alphabetArray;

After forming the array we will implement the scrolling of tableview with reference to the alphabet touched on scroller. For this we will implement the function  – (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index . Add the following lines of code in above function.

NSIndexPath *indexpath;
for (int i=0; i < alphabetArray.count; i++)
{
    NSString *titleToSearch=[alphabetArray objectAtIndex:i];  //getting sectiontitle from array
    if ([title isEqualToString:titleToSearch])  // checking if title from tableview and sectiontitle are same 
    {
        indexpath=[NSIndexPath indexPathForRow:0 inSection:i];
        // scrolling the tableview to required section
        [self.tableView scrollToRowAtIndexPath:indexpath atScrollPosition:UITableViewScrollPositionTop animated:YES]; 
        break;
    }
}
return indexpath.section;

Now run your project . Your project will look like this. Final and will function same as what you saw in the starting demo except for deleting and adding statements in tableview which are yet to be implemented. For deleting rows from tableview, we have to implement its delegate function -(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath. Add following lines of code for deleting rows in above function.

NSString *statementToBeDeleted=nil;
NSMutableArray* rowArray=[[NSMutableArray alloc]initWithCapacity:0];
if (editingStyle == UITableViewCellEditingStyleDelete)
{
    rowArray=[self getArrayOfRowsForSection:indexPath.section]; //getting the row content for a section
    statementToBeDeleted=[rowArray objectAtIndex:indexPath.row]; //getting the object to be deleted

    //removing object from the original content array
    [statementArray removeObject:statementToBeDeleted];
    //deleting content from tableview
    [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
    //again creating the alphabet array for section titles and index scroller
    [self createAlphabetArray];
    //reloading the tableview for preventing project from crashing
    [[self tableView]reloadData];
}

Run your project and check for your delete operation if working or not. It should function like the starting demo except for add operation .

For implementing add operation in tableview, go to your storyboard  and add a Navigation Bar to your tableview. To the Navigation Bar add a Bar Button Item.

For  references on how to add Navigation Bar on UITableview, check this video tutorial

Set the title of Navigation Bar to “Statement Table”.

Now, ctrl-drag from that add button to the implementation file TableViewController.m and create a method  addNewStatement  for implementing an additional view which will be used for adding statements.

Now under this function add the following lines of codes which will create a UIView with animation , a UITextView and two buttons , Save  and Cancel on the same UIView programmatically.

self.tableView.scrollEnabled=NO;  //disabling scroll of tableview

//Creating a UIView on which whole functionality is to be implemented
CGRect statementAdderFrame=CGRectMake(20, -480, 280, 440);
statementAddingView=[[UIView alloc]initWithFrame:statementAdderFrame];
statementAddingView.backgroundColor=[UIColor whiteColor];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.4];
statementAddingView.frame=CGRectMake(20,20, 280, 440);

translucentView=[[UIView alloc]initWithFrame:CGRectMake(0,0, 320, 480)];
translucentView.backgroundColor=[UIColor colorWithRed:0.0/255.0 green:0.0/255.0 blue:0.0/255.0 alpha:0.7];
[self.view.window addSubview:translucentView];
[translucentView addSubview:statementAddingView];

// creating a UILabel for Enter Statement label
CGRect enterStatementLabelFrame= CGRectMake(20, 20, 240, 40);
UILabel *enterStatementLabel=[[UILabel alloc]initWithFrame:enterStatementLabelFrame];
enterStatementLabel.backgroundColor=[UIColor clearColor];
[enterStatementLabel setFont:[UIFont fontWithName:@"Helvetica" size:16]];
enterStatementLabel.text=@"Enter Statement:";
enterStatementLabel.userInteractionEnabled=NO;
enterStatementLabel.textAlignment=NSTextAlignmentLeft;
[statementAddingView addSubview:enterStatementLabel];

//cretaing a UITextView for entering statement to be added
CGRect statementTextViewFrame=CGRectMake(20, 85, 240,130);
statementTextView=[[UITextView alloc]initWithFrame:statementTextViewFrame];
statementTextView.backgroundColor=[UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.3];
statementTextView.textColor=[UIColor blackColor];
[statementTextView setFont:[UIFont fontWithName:@"Helvetica" size:15]];
[statementAddingView addSubview:statementTextView];
[UIView commitAnimations];

//creating add button for adding statements to tableview
UIButton *addButton=[UIButton buttonWithType:UIButtonTypeCustom];
addButton.frame=CGRectMake(20, 365, 70, 30);
[addButton setTitle:@"Save" forState:UIControlStateNormal];
addButton.tag=1;
[addButton setBackgroundColor:[UIColor colorWithRed:85.0/255.0 green:255.0 blue:85.0/255.0 alpha:1.0]];
[addButton.titleLabel setFont:[UIFont fontWithName:@"Helvetica-Bold" size:16]];
[addButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
//declaring the selector and target for the Save button
[addButton addTarget:self action:@selector(statementAdded:) forControlEvents:UIControlEventTouchUpInside];
[self.statementAddingView addSubview:addButton];

//creating cancel button for cancelling whole operation
UIButton *cancelButton=[UIButton buttonWithType:UIButtonTypeCustom];
cancelButton.frame=CGRectMake(190, 365, 70, 30);
[cancelButton setTitle:@"Cancel" forState:UIControlStateNormal];
cancelButton.tag=2;
[cancelButton setBackgroundColor:[UIColor colorWithRed:255.0 green:85.0/255.0 blue:85.0/255.0 alpha:1.0]];
[cancelButton.titleLabel setFont:[UIFont fontWithName:@"Helvetica-Bold" size:16]];
[cancelButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
//declaring the selector and target for the Cancel button
[cancelButton addTarget:self action:@selector(statementAdded:) forControlEvents:UIControlEventTouchUpInside];
[self.statementAddingView addSubview:cancelButton];

// a tap gesture for hiding the keyboard when entering of statement is over
UITapGestureRecognizer *tapOnView=[[UITapGestureRecognizer alloc]init];
[tapOnView addTarget:self action:@selector(hideKeyboard:)];  //declaring the selector for tap gesture
[tapOnView setNumberOfTapsRequired:1];
[tapOnView setNumberOfTouchesRequired:1];
tapOnView.enabled=YES;
[statementAddingView addGestureRecognizer:tapOnView];

Lets, implement the selector declared for both buttons and the tap gesture. Add the following function to your program for implementing selector of tap gesture.

-(void)hideKeyboard:(UITapGestureRecognizer *)sender
{
    //hide keybaord
    [statementTextView resignFirstResponder];
}

After the above function add the following function to implement the selectors for the two buttons,  Save and Cancel , i.e. -(void)statementAdded:(UIButton *)sender

if (sender.tag==1)  //checking if button was "Save" button
{
    if ([statementTextView.text isEqualToString:@""] )  // if no statement is added to text view
    {
        UIAlertView *notAddedAlert=[[UIAlertView alloc]initWithTitle:@"Error" message:@"No statement to add" delegate:self cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil];
        [notAddedAlert show];
    }
    else
    {
        //providing animations for release of the UIView
        [UIView animateWithDuration:0.4
                              delay:0.0
                            options:UIViewAnimationOptionTransitionNone
                         animations:^{

                             statementAddingView.frame=CGRectMake(20,-480, 280, 440);

                         }
                         completion:^(BOOL finished)  //completion handler for animation
             {
                 //adding codes which are to be implemented after animation is over
                 self.tableView.scrollEnabled=YES;  //enabling scroll of UIView
                 [translucentView removeFromSuperview];

                 NSString *statementToAdd=self.statementTextView.text;
                 [statementArray  addObject:statementToAdd];  //adding statement to original array
                 [statementArray sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)];  //sorting the array
                 [self createAlphabetArray];  // creating alphabet array for section title and index scroller
                 [self.tableView reloadData];  //reloading the tableview for updation

                 //code for scrolling of tableview to the position where statement is added
                 NSMutableArray *rowContainer=[[NSMutableArray alloc]initWithCapacity:0];
                 NSString *sectionString=[statementToAdd substringToIndex:1];
                 NSInteger sectionIndex=[alphabetArray indexOfObject:sectionString];
                 for (NSString *title in statementArray)
                 {
                     NSString *rowTitle=[title substringToIndex:1];
                     if ([rowTitle isEqualToString:sectionString])
                     {
                         [rowContainer addObject:title];
                     }
                 }
                 NSInteger rowIndex=rowContainer.count-1;
                 [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex] atScrollPosition:UITableViewScrollPositionTop animated:YES];

           }];
           [UIView commitAnimations];
    }
}
else  // if the button is "Cancel" button
{
    // providing animations for release of UIView
    [UIView animateWithDuration:0.4
                          delay:0.0
                        options:UIViewAnimationOptionTransitionNone
                     animations:^{
                         statementAddingView.frame=CGRectMake(20,-480, 280, 440);
                 }
                     completion:^(BOOL finished) //completion handler for animation.
     {
         //adding codes which are to be executed after animation is oover
         self.tableView.scrollEnabled=YES;  // enabling scroll of tableview
         [translucentView removeFromSuperview];
     }];
     [UIView commitAnimations];
}

 

Your  project is complete and now press ctrl+R to run it. Your simulator output will be somewhat like this.

 

illustration You can download the complete project from here.