Apr 09

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.

27 Responses to “iPhone Tutorial: Adding a Calendar”

  1. walkman says:

    when i open it in xcode it works. when i copy it into my project (calendar folder) it gives me error

    duplicate symbol _main

  2. walkman says:

    ok, pls disregard. found that main.m was copied to calendar folder for some reason via Finder. even if i did not select it

  3. ABC says:

    It sometimez happens..wen u mistakenly select files when adding existing files to project. may b it heppnd in ur case.

  4. Maulik says:

    Hi
    Great tutorial. Its was biggest hurdle for iphone developers using a calendar. This tutorial helped a lot in directly implementing a calendar in a project.

    I wanted to have different type of calender… Like a day view calendar

    This is the image url of what i am trying to say:
    http://www.kyoobed.com/applications/clockedin/iphone/images/Day-View.png

    Any ideas on how to implement this.. It would be great if u can throw some light to this as most of the business applications require such kind of calenders too.

  5. ABC says:

    @Maulik:
    the view you mentioned is much simpler. It is just a tableView with indexes.

  6. Maulik says:

    Hi ABC,
    Thanks for the quick reply.

    I didnt get the exact idea of how is it a table view with indexes because I have always seen indexes at the top of each section. So it should have been like this:

    10 AM:
    Some text here
    11 AM:
    Some text here

    So in this 10AM and 11AM are the indexes and “Some text here” is the data within the index

    But the format here is

    10 AM: Some text here

    11 AM: Some text here
    and so on

    Even if you look closely the event if it starts at 10:15 then the coloured box is also drawn below the mark of 10:00 AM

    I hope you are getting what I am trying to explain.

    It would be great if you could show some coding example or at least some way to achieve this. I would be very obliged to you if you show me some way.
    Thank you ABC.

  7. ABC says:

    Hi Maulik,
    Sorry for the late reply. Yes I understand the issues you mentioned. What you can do is to create your own custom cells. You have full control over every thing then and you can layout subviews exactly the way you want.
    I will write 1 example like this, but that will take some time coz m really consumed wid office works.

    Let me know if you wer able to fix it.

  8. Maulik says:

    Hi ABC,
    No issues for the late reply. I am working on it and will definitely let you know once i fix it. I am not sure whether i will be able to do it or not as I am not expert like you but will definitely give hands on it.
    I will be awaiting for your example too.
    Meanwhile, I came across one more issue dont know whether this is the right place to post it or not, but the issue is I wanted to create a accordion style table view. What i mean is, I have a table view which has the list of cities.Once i tap on one of the cells in the table it should expand and the list below it should move down. The description and detail of the city should appear in the expanded list. If i tap again on the same cell it will collapse. I hope you got the idea that I am trying to tell you. If possible let me know any way to achieve this. I have found a project on code.google related to MAC OS X but not related to iPhone. If you want i will post the link over here. The code is very complicated and scattered.

    I would like you to show some way to achieve this as this is a very immediate thing i need to accomplish.

    Thanks for the reply. Awaiting for your suggestion on this. You can also mail me on bcod.maulik@gmail.com

  9. Maulik says:

    Hi ABC,
    No issues for the late reply. I am working on it and will definitely let you know once i fix it. I am not sure whether i will be able to do it or not as I am not expert like you but will definitely give hands on it.
    I will be awaiting for your example too.
    Meanwhile, I came across one more issue dont know whether this is the right place to post it or not, but the issue is I wanted to create a accordion style table view. What i mean is, I have a table view which has the list of cities.Once i tap on one of the cells in the table it should expand and the list below it should move down. The description and detail of the city should appear in the expanded list. If i tap again on the same cell it will collapse. I hope you got the idea that I am trying to tell you. If possible let me know any way to achieve this. I have found a project on code.google related to MAC OS X but not related to iPhone. If you want i will post the link over here. The code is very complicated and scattered.

    I would like you to show some way to achieve this as this is a very immediate thing i need to accomplish.

    Thanks for the reply. Awaiting for your suggestion on this

  10. ABC says:

    Hi Maulik,
    The seond issue you mentioned is quite easy. All you have to do is INSERT records between a transaction which starts with
    [tableView beginUpdates] and ends with [tableView endUpdates].

    Will paste an example code today by evening.

  11. Maulik says:

    Gr8
    Awaiting for your example code. Thank you very much for all the pain and time you are giving to resolve such issues.

  12. ABC says:

    Hi Maulik, Sorry for getting late:
    For dynamically inserting rows you do this:
    [myTableView beginUpdates];
    [myTableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationTop];
    [myTableView deleteRowsAtIndexPaths:indexPaths2 withRowAnimation:UITableViewRowAnimationTop];
    [myTableView endUpdates];

    Now lets assume in your case: You have an array of cities called “cities”. and you have a dictionary that holds an array of data for every city. I suppose this is the data that will be displayed against a city when it is tapped.

    Now you will have to keep track of the status of each cell. Wether it is already expanded or it is in compressed form. So you make an array called mainCellStatus and initialize it like this:
    for(int i=0; i<[cities count]; i++)
    [mainCellStatus addObject@"compressed"]; //since all cells are compressed in beginning. Note that u shud actually make an array of integer rather den strings…

    So your tableView Delegate methods should look like this

    numberOfSections .. { return [cities count]; } // this is obvious..u r making as many sections as number of cities.


    -(NSInteger)numberOfRowsInSection:(NSInteger)section //this 1 s a lil tricky
    {
    //check if the section is in compressed state or not
    if([[status objectAtIndex:section] isEqualToString:@"compressed"])
    return 1; //coz in compressed state only the city will be displayed
    else{
    //we need to get the corresponiding array of data first
    NSString *city = [cities objectAtIndex: section];
    NSArray *arrayForThisCity = [dictionary objectForKey: city];
    return 1+[arrayForThisCity count]; // have added 1 because number of rows in that section should be 1(City) + n(number of data elements for that city);
    }

    Ok Now the cellForRow method!

    //this is simple just check the cell is first row of section or not.
    if(indexPath.row == 0)
    cell.text = [cities objectAtIndex: indexPath.section];
    else
    [arrayForCity objectAtIndex: indexPath.row-1]; //obtain arrayForCity like we did in prev method

    Now the most tricky part! didSelect delegate method of tableView..

    {
    //first find if the cell tapped is the first row of the section or not. Coz you don want to compress the section if he taps on child rows of a city..
    if(indexPath.row == 0)
    return; //do nothing in this case
    //now check if the section is compressed or expanded.
    if([[ status objectAtIndex: indexPath.section] isEqualToString:@"compressed"])
    {
    //create indexPaths for rows to be inserted.
    NSMutableArray *arrayOfIndexPaths = [[NSMutableArray alloc] init];
    for(int i=0;i<[arrayForThisCity count]; i++)
    {
    NSIndexPath *iP = [NSIndexPath indexPathForRow:(i+1) inSection:indexPath.section];
    }

    [myTableView beginUpdates];
    [myTableView insertRowsAtIndexPaths:arrayOfIndexPaths withRowAnimation:UITableViewRowAnimationTop];
    [myTableView endUpdates];

    //DONT FORGET TO MARK THE STATUS
    [status replaceObjectAtIndex:indexPath.section withObject:@"expanded"];
    }
    similarly use this if the rows were already expanded.
    [myTableView insertRowsAtIndexPaths:arrayOfIndexPaths withRowAnimation:UITableViewRowAnimationTop];

  13. Maulik says:

    Thanks for showing this. But One of the ways i tried and implemented was a bit or i can say far more easier than this what you showed. I have issues in this also which i will tell you later but first tell me whether this is also a good way of doing the same thing or not?

    In my method i have take a static integer say “myrow”.

    Now on didselectrowatIndexpath I assign the indexpath of the cell tapped to this “myrow”.

    and then reload the table data. Now when i reload table data heightForRowAtIndexPath is called in which i have the following code:
    if(indexPath.row == myrow)
    {
    return 120;
    }
    else
    {
    return 50;
    }

    So this increases the height of the cell that i clicked. I have placed a label in the customcell which is below the title of the cell so it will be only visible if u increase the height of the cell.Like this i can place many labels in the cell below each other but they will be visible only when the height of the cell is increased.

    Again there is an issue in this: The second last and the last row of the cell shows this labels which are below it and shouldnot be visible untill you expand them. But i guess at the second last and last row the cell is utilising the area below it which is a free space.
    I dont know whether i am trying the right thing or not. I will definitely try the way you have shown but since i am not very strong with the NSDictionary part i am a bit confused about that.

    Do give me review about the way i showed you.

  14. ABC says:

    Yah the approach sounds good. I inserted/deleted new rows at run time, where as you are initializing everything at start and hiding/showing it by manipulating the height of row.

    M not sure how you can fix the last row issue. I think you should read the documentation of UITableView for the fix. Or what you can do is insert a blank cell at the end.

  15. Satish says:

    Hi,
    Great Tutorial, Im facing issues while integrating CalendarTest with my project. Im doing switch between two views from the MainViewController using buttons, Now i want to one of the Views to be Calendar, so can you tell me how can i add CalendarTest to my view and still be controlled by one MainViewController…..

    Thanks in advance…

  16. Maulik says:

    Hi ABC,
    You said that making a calendar control like this is a very simple

    http://www.kyoobed.com/applications/clockedin/iphone/images/Day-View.png

    Can you please elaborate more on this? Or if you can make a sample of this it would be of great help. I need to implement this some how.

  17. Maulik says:

    Anyways i made this above one. So no issues now.

  18. Srikanth says:

    Can you please guide me how to make text in cell that displaying the date to be center aligned in cell [Currently it is Left Top aligned]……Also i want to change the color, how to find the particular color RGB value…?

  19. James N says:

    How would you go about selecting and deselecting a tile back to its original state color and default state? Is there a way to not have to redraw the entire calendar view? And perhaps only just the selected tile?

  20. brd says:

    Is it possible to copy names from the contacts database and import them to the calendar???

  21. Simo says:

    Great tutorial and great code. I have one complaint or one change suggestion to make the code a little more apple compliant.

    When you create a calendarView via -initWithFrame:delegate: the delegate method -didChangeMonth is called.

    This is a problem because you have not returned the calendar view, so the delegate has no reference to it. Thus the delegate must add an unnecessary error check. There are many ways to change the code to fix this problem, but I believe the apple way of doing things is to change the delegate method to something like.
    -calendarViewDidChangeMonths:(KLCalendarView *)calView
    This is similar to how the UITableViewDelegate and UITableViewDataSource methods work

  22. Mu says:

    How can i add events/appoinments on calendar.
    Thanks in advance!!!!

  23. riken says:

    is there another link to download the project?
    the one above doesn’t work
    thanks

  24. Rahul says:

    we try this code but i got 16 errors in my project which are “some classes reference not found” so plz tell me how to add calendar folder in our project and how get the reference of that folder…plz give the replay as early as possible…..

  25. Chris says:

    Love the work you put into this!
    I have a problem where the data is being retrieved from a feed but only a month’s worth at a time. So I’ve managed to fetch the data in the didChangeMonths but the tiles are redrawn before the data is fully fetched. Is there a way to force a redraw of all the tiles?

  26. Hi, anybody knows how to put the data from Google Calendar to this app?

Leave a Reply

preload preload preload