Building iRemember – shopping list app, part 3

We’re continuing building our iRemember application.
Now it is time to focus on application UI. We’ll start with calendar selection and then – showing reminders from selected calendar.
We’ll also implement state restoration (links are in Russian) for user’s convenience.

Here is our storyboard so far.
View Controllers
We’re defining UINavigationController and two UITableViewControllers. We’re going to add Storyboard ID and Restoration ID to each controller. This will help us implement state preservation in our application (and also define classes for UITableViewControllers).
Navigation Controller

Calendars Controller
Having these set, we’re also going to enable refresh control (“pull to refresh” is now part of iOS SDK, so let’s use it) on our UITableViewControllers.
Refresh Control
In the ideal world where all development tools are working properly we would just set action of refresh control to our view controller method. But due to bug in Xcode 4.6 we have to do it manually in code (each UITableViewController has refreshControl property).

- (void)viewDidLoad
    [super viewDidLoad];
    [self.refreshControl addTarget:self action:@selector(refresh) forControlEvents:UIControlEventValueChanged];

Now let’s focus on our view controllers, starting with IRCalendarsViewController which shows reminder lists (or, calendars according to EventKit).

    NSMutableArray *sources = [NSMutableArray array];
    NSArray *calendarsArray = [[IRReminderManager defaultManager] reminderCalendars];
    NSMutableDictionary *calendars = [NSMutableDictionary dictionary];
    [calendarsArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        EKCalendar *calendar = obj;
        NSMutableArray *calendarsForSource = calendars[calendar.source.sourceIdentifier];
        if (!calendarsForSource)
            calendarsForSource = [NSMutableArray array];
        if (![sources containsObject:calendar.source])
            [sources addObject:calendar.source];
        [calendarsForSource addObject:calendar];
        calendars[calendar.source.sourceIdentifier] = calendarsForSource;
    self.sources = sources;
    self.calendars = calendars;

    [self.refreshControl beginRefreshing];

    self.navigationItem.rightBarButtonItem.enabled = [IRReminderManager defaultManager].accessGranted;
    [self fillCalendars];
    [self.tableView reloadData];
    [self.refreshControl endRefreshing];

-fillCalendars method fills sources array and calendars dictionary properties. sources contains list of EKSources which have at least one calendar. calendars dictionary contains array of EKCalendars for each sourceIdentifier. This way we’ll group our calendars with sources. So, implementation of table view data source would be simple.

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
    return self.sources.count;

- (NSInteger)tableView:(UITableView *)tableView 
    return [self.calendars[[self.sources[section] sourceIdentifier]] count];

- (UITableViewCell *)tableView:(UITableView *)tableView 
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
    static NSString *CellIdentifier = @"CalendarCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier 
    EKCalendar *calendar = self.calendars[[self.sources[indexPath.section] sourceIdentifier]] [indexPath.row];
    cell.textLabel.text = calendar.title;
    return cell;

-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
    return [self.sources[section] title];

I’m using new Objective-C syntax for accessing array and dictionary elements, so [‘s and ]‘s are everywhere, but I think the code is still readable.

IRCalendarsViewController does not have specific properties to save for state preservation. However, there is one thing we want to keep. It is table view cell selection. When we select specific list it gets highlighted and new controller is added to navigation stack. When we get back from that controller selection fades out. And we want to keep that behavior when application is restored. Fortunately, this is very easy to do. We have to adopt UIDataSourceModelAssociation protocol.

- (NSString *) modelIdentifierForElementAtIndexPath:(NSIndexPath *)idx 
                                             inView:(UIView *)view
    EKCalendar *calendar = self.calendars[[self.sources[idx.section] sourceIdentifier]] [idx.row];
    return calendar.calendarIdentifier;

- (NSIndexPath *) indexPathForElementWithModelIdentifier:(NSString *)identifier 
                                                  inView:(UIView *)view
    NSIndexPath * __block indexPath;
    [self.sources enumerateObjectsUsingBlock:^(id obj, NSUInteger sourceIdx, BOOL *sourceStop) {
        EKSource *source = obj;
        [self.calendars[source.sourceIdentifier] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            EKCalendar *calendar = obj;
            if ([calendar.calendarIdentifier isEqualToString:identifier])
                indexPath = [NSIndexPath indexPathForRow:idx inSection:sourceIdx];
                *stop = YES;
                *sourceStop = YES;
    return indexPath;

These methods map each table view cell to its identifier. This way tableView will be able to save scroll position to specific element, not just indexPath. Also, highlighted element will be restored automatically.

We’ll continue building UI for our application in next post – we’ll implement display of our reminders. We’ll also cover stare restoration again in more details.

For more information on EventKit, consult Apple documentation – Calendar and Reminders Programming Guide, Event Kit Framework Reference, State preservation guide, UIDataSourceModelAssociation reference.
Source code for this tutorial is available on GitHub.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s