Thursday, April 9, 2009

iPhone Development: Creating Native Contacts like screen

Hello again. This time I will be creating a native contacts like screen. If you are not familiar with it, here is how it looks like:


This view is very important for many business applications. There can be several ways of implementing this view, depending upon your specific need you can write one. I will discuss the simplest one in which I will be creating a dictionary to access names. I will be adding code to same DatabaseTest project which I created earlier. 
You can find DatabaseTest here

So lets start and open the project DatabaseTest. We need not change anything in AppDelegate. Open RootViewController.h and add the following code:

@interface RootViewController : UITableViewController {

//NSMutableArray *tableData;

NSMutableArray *arrayOfCharacters;

NSMutableDictionary *objectsForCharacters;

}


@end


You will notice that I have commented tableData and declared an additional array and a dictionary. We wont be using tableData anymore instead data will be now stored in dictionary objectsForCharacters. You will understand how in some time now.

Open RootViewController.m and in -(void)initializeTableData comment all the code written and add this code:

arrayOfCharacters = [[NSMutableArray alloc]init];

objectsForCharacters = [[NSMutableDictionary alloc]init];

sqlite3 *db = [DatabaseTestAppDelegate getNewDBConnection];

for(char c='A';c<='Z';c++)

{

NSMutableString *query;

query = [NSMutableString stringWithFormat:@"select name from user where name LIKE '%c",c];

[query appendString:@"%'"];

char *sql = [query cString];

sqlite3_stmt *statement = nil;

if(sqlite3_prepare_v2(db,sql, -1, &statement, NULL)!= SQLITE_OK)

NSAssert1(0,@"error preparing statement",sqlite3_errmsg(db));

else

{

NSMutableArray *arrayOfNames = [[NSMutableArray alloc]init];

while(sqlite3_step(statement)==SQLITE_ROW)

[arrayOfNames addObject:[NSStringtringWithFormat:@"%s",(char*)sqlite3_column_text(statement, 0)]];

if([arrayOfNames count] >0)

{

[arrayOfCharacters addObject:[NSString stringWithFormat:@"%c",c]];

[objectsForCharacters setObject:arrayOfNames forKey:[NSString stringWithFormat:@"%c",c]];

}

[arrayOfNames release];

}

sqlite3_finalize(statement);

}


I will explain what we are doing here. For each alphabetical character we are querying database to get all the names starting with that character. If we get 1 more or more names for a character, we add that character in an array arrayOfCharacters. Names are stored in a separate array arrayOfNames. This is a temporary array which will added to dictionary objectsForCharacters. At the end we have arrayOfCharacters holding all those characters which have at least one name starting with them respctively and we have objectsForCharacters holding an array of names for each character in arrayOfCharacters.

Now we need to fix the view. For this we will now be using 3 new tableView methods:

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


- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section


The first method asks for an array of strings which it will use as index titles. You can find indexes A-Z vertically arranged on the rightmost side of tableView. Here you can decide either to give all the characters or just the ones in arrayOfCharacters.

In the second method you specify what should be the section number for the given index title.

In third method you provide the title for each section header. You can find in the first figure, a gray bar with a character appearing in it just above each section.

So lets implement these methods, add the following code:

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

NSMutableArray *toBeReturned = [[NSMutableArray alloc]init];

for(char c = 'A';c<='Z';c++)

[toBeReturned addObject:[NSString stringWithFormat:@"%c",c]];

return toBeReturned;

}

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {

NSInteger count = 0;

for(NSString *character in arrayOfCharacters)

{

if([character isEqualToString:title])

return count;

count ++;

}

return 0;// in case of some eror donot crash d application 

}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {

if([arrayOfCharacters count]==0)

return @"";

return [arrayOfCharacters objectAtIndex:section];

}


I think I have already explained the code. 

Before we build and run the project, we need to change the other tableView methods we created last time. Make sure these three methods look like these:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

return [arrayOfCharacters count];

}



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

return [[objectsForCharacters objectForKey:[arrayOfCharacters objectAtIndex:section]] count];

}



- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *MyIdentifier = @"MyIdentifier";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];

if (cell == nil) {

cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];

}

// Set up the cell

cell.text = [[objectsForCharacters objectForKey:[arrayOfCharacters objectAtIndex:indexPath.section]] objectAtIndex:indexPath.row];

return cell;

}


I will try explaining a few thing here.    [[objectsForCharacters objectForKey:[arrayOfCharacters objectAtIndex:section]] count
Here we have first derived the character for the section. Then we used that character to obtain its corresponding array of names. And the count of names in the array will be numberOfRows in that section.
Rest should be clear.
Go ahead and run the project and you should see the screen similar to figure 1.

Also try adding more data into database and see how it looks.

10 comments:

  1. How to I make it so the user can add there own contact?

    ReplyDelete
  2. Not just for contacts. Normally in business applications, you need such screens for showing contacts, leads or anything that is to be searched by its name.

    ReplyDelete
  3. Your code syntax coloring is great, but look terrible over the Black background that you have for your blog. Perhaps a lighter backgroung would do well.

    Ajay

    ReplyDelete
  4. can I make the indexes appearing in the right most bar clickable? such that when I click on one index item/character, it gets me all the matching records for that character.

    ReplyDelete
  5. Hai,

    Its nice, but in my case, I am getting the Json objects from server. How can i proceed ? (Sorting the Json object with KeyValue and displaying alphabetic indexed on rightSide of the TableView. Json object contain "Name", "Desc", "Address" these keyValues.)

    ReplyDelete
  6. I got it to run but when I press/select a letter from the alphabets index values (on the rightSide of the TableView), it displays the letter at the top of the device, but does not go to those words. What did I miss?

    ReplyDelete
  7. Hi, I have an image in my SQLite table for each record which I would like to display on the left of the row content. How would I change the code for this to happen.

    BTW, great tutorials.

    ReplyDelete
  8. Assuming you have already stored image in database, you retrieve it like this:

    NSData *data = [[NSData alloc] initWithBytes:sqlite3_column_blob(statement, 0) length:sqlite3_column_bytes(statement, 0)];

    UIImage *image = [UIImage imageWithData:data];

    ReplyDelete
  9. Hello,
    Excellent code posted,
    Thamks for sharing such a helpfull code.


    hire iphone developer

    ReplyDelete

Leave your suggestions