Context

Tapku Calendar displaying markers

It's been a quiet few months on Developing in the Dark. I've found myself consumed in iOS development both at work and in any spare time I can find (which is never as easy to find as you'd like it to be). A recent project at work, Nightvibes, required the development of an iPhone app to bring the location-based, social planning app to the mobile. The app has thrown me in the deep end of iOS development, but has given me the chance to grapple with a bunch of development patterns, Objective C and iOS SDK, third party libraries, and all the mysterious iOS quirks that push your sanity to the edge.

Early on I had to implement a drop down calendar with a similar look and feel as the native calendar app. There appears to be two major, actively maintained implementations on Github: Kal and Tapku Library - I went with Tapku, however Kal could possibly be just as good or even better (let me know if it is). Tapku is super easy to integrate and offers a bunch of UI components including Coverflow, Progress Bars, Graphs, and importantly in this instance - a Calendar.

Calendar app using look and feel Tapku is based on

Adding the Calendar to your project is a quick process, however there isn't much documentation around on how to implement the calendarMonthView:marksFromDate:toDate: datasource delegate method to add markers/dots to days in the calendar (as seen in the screenshot on the right). In Nightvibes this is an important feature in showing, at a glance, which nights your friends are heading out. I eventually stumbled cross a random japanese website that described a solution. Using that solution as a basis, I've adapted it mainly to handle some daylight saving anomalies I came across (see references). I've included a demo project and code snippets to describe the solution, hopefully it'll help to make your implementation fairly straight forward.

To the point

Tapku Xcode project folder structure

First step is to get the calendar itself. Head over to Github to clone or download Tapku. In the demo project I only imported the files the Calendar component needs into the project (see image to your right). I've just labelled the groups "Libraries" and "Tapku Library" based on personal file organising preferences. Place them wherever suits your project, just make sure you have those .h and .m files and the TapkuLibrary.bundle.

At this point its best to look over and refer to the demo project which gives a more complete view on the code, how to import TKCalendarMonthView, initialise a new calendar and the code for sliding in/out the calendar from the top of the device.

To add markers you'll need to implement the following datasource delegate method. The method returns an NSArray with TRUE/FALSE values for each day (in order) between the startDate and lastDate to indicate which days should have markers.


- (NSArray *)calendarMonthView:(TKCalendarMonthView *)monthView marksFromDate:(NSDate *)startDate toDate:(NSDate *)lastDate {
	NSMutableArray *marks = [NSMutableArray array];

	....

	return [NSArray arrayWithArray:marks];
}

Within the marksForDate method you'll need to wire up a source for determining which days should have markers. The demo and code snippet use an NSArray of hardcoded strings, however you'll most likely want to get this data from CoreData or possibly a web service.


NSArray *data = [NSArray arrayWithObjects:@"2011-01-01 00:00:00 +0000", nil];

The following is the slightly more complex part that is the basis of building dates that remove daylight saving issues with TKCalendarMonthView by creating dates that always have 00:00:00 for the hours, minutes and seconds. It is important that both the dates this method uses and the dates in your "data" array are set to have this 00:00:00 time basis so that comparisons and containsObject: method calls can be accurate. The key part to this snippet is the timeZoneForSecondsFromGMT method that removes daylight savings from the calculation. Also making sure NSDate *d is formed from NSDateComponents is important in future offsets calculations.


NSCalendar *cal = [NSCalendar currentCalendar];
[cal setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];

NSDateComponents *comp = [cal components:(NSMonthCalendarUnit | NSMinuteCalendarUnit | NSYearCalendarUnit | NSDayCalendarUnit | NSWeekdayCalendarUnit | NSHourCalendarUnit | NSSecondCalendarUnit) fromDate:startDate];

NSDate *d = [cal dateFromComponents:comp];

The code then initialises a new NSDateComponents object that sets an offset of 1 day that can be later called within the "while" loop for incrementing the date as it loops through the month's date range.


// Init offset components to increment days in the loop by one each time
NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];
[offsetComponents setDay:1];

There are three parts to this loop. The first checks whether the loop date is greater than the last date, in which case we want to break out of the loop and move on. If the loop date is still in the range of dates then it is looked up in the "data" array, if it exists then a TRUE value is added to the marks array, else FALSE. Finally the date is incremented for the next loop iteration.


// for each date between start date and end date check if they exist in the data array
while (YES) {
	// Is the date beyond the last date? If so, exit the loop.
	// NSOrderedDescending = the left value is greater than the right
	if ([d compare:lastDate] == NSOrderedDescending) {
		break;
	}

	// If the date is in the data array, add it to the marks array, else don't
	if ([data containsObject:[d description]]) {
		[marks addObject:[NSNumber numberWithBool:YES]];
	} else {
		[marks addObject:[NSNumber numberWithBool:NO]];
	}

	// Increment day using offset components (ie, 1 day in this instance)
	d = [cal dateByAddingComponents:offsetComponents toDate:d options:0];
}
[offsetComponents release];

That's really all there is to it. Quite simple, yet it makes a big difference in apps where the calendar is a key component. Below are screenshots of the demo project and the of the code being used in Nightvibes. Grab the demo code below and enjoy!

Tapku demo calendar hidden Tapku demo calendar visible Nightvibes app implementing Tapku calendar

Get the code

The demo code shows how to implement the Tapku Calendar, add markers (including resolving daylight savings issues by using NSCalendar and NSDateComponents) and enable slide in/out calendar animations.

References


Update: Tab Bar Implementation Example

28th April 2011 - I've picked up that a few people are keen to see the Tapku Calendar implemented in a tab bar application. Below you can download an example project that closely replicates the method used in our Nightvibes iPhone app. Hopefully it helps anyone needing a similar implementation.