Tuesday, April 28, 2009

iPhone Development: Custom Cells

Ohhk, quite a lot of things done but this one is something which I have used most often. Custom cells can sometimes greatly push ahead the usability of the application. In this post I am going to create a test project which will demonstrate how to create custom cells and use them appropriately to provide better usability. The application will finally look like this:


So to start open xcode and create a new project, chose the template as "Navigation Based" and  name it as "CustomCellTestProject". What template you chose does not matter, refer my previous posts to find how you can start working on any template.
First thing we will do is create a customCell. Right click on Classes and add a new UITableViewCell subclass. Name it as "CustomCell". Now open CustomCell.h and add the following code:

#import


@interface CustomCell : UITableViewCell {

UILabel *primaryLabel;

UILabel *secondaryLabel;

UIImageView *myImageView;


}

@property(nonatomic,retain)UILabel *primaryLabel;

@property(nonatomic,retain)UILabel *secondaryLabel;

@property(nonatomic,retain)UIImageView *myImageView;

@end


Here we have simply added a primary label to display the primary text, a secondary label and an imageView. These elements will be created and added into the content view of our custom cell. So open CustomCell.m and add the following code

- (id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier {

    if (self = [super initWithFrame:frame reuseIdentifier:reuseIdentifier]) {

        // Initialization code

primaryLabel = [[UILabel alloc]init];

primaryLabel.textAlignment = UITextAlignmentLeft;

primaryLabel.font = [UIFont systemFontOfSize:14];

secondaryLabel = [[UILabel alloc]init];

secondaryLabel.textAlignment = UITextAlignmentLeft;

secondaryLabel.font = [UIFont systemFontOfSize:8];

myImageView = [[UIImageView alloc]init];

[self.contentView addSubview:primaryLabel];

[self.contentView addSubview:secondaryLabel];

[self.contentView addSubview:myImageView];

    }

    return self;

}


We create the three elements and added them to the contentView of our cell. Also don't forget to synthesize all the three elements as we are going to access these elements from other classes.

@synthesize primaryLabel,secondaryLabel,myImageView;


Now, we have already added the UI elements into our cell but you must have noticed, we have not yet defined how these elements will appear inside cell. Go ahead and add the following code for that:

- (void)layoutSubviews {

     [super layoutSubviews];

CGRect contentRect = self.contentView.bounds;

CGFloat boundsX = contentRect.origin.x;

CGRect frame;


frame= CGRectMake(boundsX+10 ,0, 50, 50);

myImageView.frame = frame;

frame= CGRectMake(boundsX+70 ,5, 200, 25);

primaryLabel.frame = frame;

frame= CGRectMake(boundsX+70 ,30, 100, 15);

secondaryLabel.frame = frame;


}


You can do anything in this method to define the lay out of cell. I have simply assigned frames to all the elements.
You can also find a method in CustomCell.m

- (void)setSelected:(BOOL)selected animated:(BOOL)animated {


    [super setSelected:selected animated:animated];


    // Configure the view for the selected state

}


This method can be used to define how your cell should react when it is selected. You can describe what should be the highlight color or may be you want to flash one of the labels anything of your choice. I am leaving this method as it is.

We are done with creating our custom cell and now we have to use it. Open RootViewController.m and import CustomCell.h at the top.


#import "CustomCell.h"


I am going to create 5 cells here, you can just use your own logic of specifying number of cells and data. So change the following method to look like this:

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

    return 5;

}


Now we are going to use our custom cell. If you look at the cellForRow method, you will find that a UItableViewCell has been created and re used. Now all we have to do is to replace this cell with our new cell. Change the code inside this method to look like this: 

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

    

    static NSString *CellIdentifier = @"Cell";

    

    CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    if (cell == nil) {

        cell = [[[CustomCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];

    }

    

    // Set up the cell...

switch (indexPath.row) {

case 0:

cell.primaryLabel.text = @"Meeting on iPhone Development";

cell.secondaryLabel.text = @"Sat 10:30";

cell.myImageView.image = [UIImage imageNamed:@"meeting_color.png"];

break;

case 1:

cell.primaryLabel.text = @"Call With Client";

cell.secondaryLabel.text = @"Planned";

cell.myImageView.image = [UIImage imageNamed:@"call_color.png"];

break;

case 2:

cell.primaryLabel.text = @"Appointment with Joey";

cell.secondaryLabel.text = @"2 Hours";

cell.myImageView.image = [UIImage imageNamed:@"calendar_color.png"];

break;

case 3:

cell.primaryLabel.text = @"Call With Client";

cell.secondaryLabel.text = @"Planned";

cell.myImageView.image = [UIImage imageNamed:@"call_color.png"];

break;

case 4:

cell.primaryLabel.text = @"Appointment with Joey";

cell.secondaryLabel.text = @"2 Hours";

cell.myImageView.image = [UIImage imageNamed:@"calendar_color.png"];

break;

default:

break;

}

    return cell;

}


I have added some dummy data. You can use your data source to provide data for primaryLabel and secondaryLabel. Please note that I have used three images here. You can use any image of your choice. All you need to to do is copy the images and paste it into your project root folder (which in this case is CustomCellTestProject folder). After pasting the files, in xcode right click on Resources (or any group), select Add >> ExistingFiles and then select all the images you want to add in you project. Once added you can simply use them by their names.

Thats it, go ahead and run the project. You will find something wrong when you compare you simulator screen with mine. If you noticed in the CustomCell.m, I have given some frames to the UI elements. You need to make sure that the height of your cell large enough to accomodate all the elements. So add this following code and you fixed the issue:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

{

return 50;

}


Let me know if I have been missing something here.




 
 

Thursday, April 9, 2009

iPhone Development: Creating Native Calendar Like View

In this post I will be creating a calendar, which could reused in an any native application.
I used the calendar source from here and modified it a little bit to suit my requirements.
You can find the modified code from my project here: DOWNLOAD PROJECT
The final screen should look like this:

You can download the complete project from the link given, I will only discuss how we can reuse this calendar and modify it to fit our specific requirements.
Open the project CalendarTest in xCode. You will find a folder Calendar containing some 20 files.
You can reuse this folder as such in any project you want show the calendar. I will be using these files to display a modified calendar in CalendarTestView header and implementation files.

Open CalendarTestView.m and find the following code:

- (void)loadView {

[super loadView];

calendarView = [[[KLCalendarView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 360) delegate:self] autorelease];

myTableView = [[UITableView alloc]initWithFrame:CGRectMake(0,260,320,160) style:UITableViewStylePlain];

myTableView.dataSource = self;

myTableView.delegate = self;

UIView *myHeaderView = [[UIView alloc] initWithFrame:CGRectMake(0,0,myTableView.frame.size.width , 20)];

myHeaderView.backgroundColor = [UIColor grayColor];

[myTableView setTableHeaderView:myHeaderView];

[self.view addSubview:myTableView];

[self.view addSubview:calendarView];

[self.view bringSubviewToFront:myTableView];

}


Here I have initialized a tableView and the calendarView with appropriate frames. You can change the frames if you want to change the respective size of tableView and/or calendarView.

Now find the Calendar Delegate methods:

- (void)calendarView:(KLCalendarView *)calendarView tappedTile:(KLTile *)aTile{

NSLog(@"Date Selected is %@",[aTile date]);

[aTile flash];

if(tile == nil)

tile = aTile;

else

[tile restoreBackgroundColor];

tile = aTile;

}



Here you need to write the code that should be executed when a tile is tapped. Typical usage of this method would be to load data for the tableView from database corresponding to the date of the tile.

- (KLTile *)calendarView:(KLCalendarView *)calendarView createTileForDate:(KLDate *)date{

CheckmarkTile *tile = [[CheckmarkTile alloc] init];

//tile.checkmarked = YES;//based on any condition you can checkMark a tile

return tile;

}


This method is called for each tile. Just like cellForRow is called for each cell. Please note that although this method will not be called more than once for a tile, but when you change the month of calendar, all tiles are redrawn, so this method will again be called for each tile.
You can find a commented line in this method! This is very important code which you can use to tell the user that they have some data corresponding to a date.
For example I have a meeting on 4th of current month. You can check if tile.date == 4th then check-mark the tile.

- (void)didChangeMonths{

UIView *clip = calendarView.superview;

if (!clip)

return;

CGRect f = clip.frame;

NSInteger weeks = [calendarView selectedMonthNumberOfWeeks];

CGFloat adjustment = 0.f;

switch (weeks) {

case 4:

adjustment = (92/321)*360+30;

break;

case 5:

adjustment = (46/321)*360;

break;

case 6:

adjustment = 0.f;

break;

default:

break;

}

f.size.height = 360 - adjustment;

clip.frame = f;

CGRect f2 = CGRectMake(0,260-adjustment,320,160+adjustment);

myTableView.frame = f2;

[self.view bringSubviewToFront:myTableView];

tile = nil;

}


This method is called every time you change the month in calendar. I have done some adjustments for fixing the free space between tableView and calendar when there are less number of weeks in a month. You can wrote your own logic for that.


Now the tableView delegate methods. You can write anything you want depending on your requirement. I have written some dummy code to take screen shots

#pragma mark tableViewDelegate Methods

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

return 1;

}


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

return 5;

}


- (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];

}

cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;

[cell setText:@"No Data For Now"];

return cell;

}


You can read data from the source which maintains data according to the last tile tapped.

Let me know if I am missing something.

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.