Building iRemember – shopping list app, part 1

iRemember
From now on I’ll try to blog in English. And I’ll start with EventKit framework tutorial.

Let’s outline our goal. We’ll create “Remember the bread”-like application. Basically, we’ll create shopping plan application with several specific requirements:

  1. Application should be able to handle several lists.
  2. User should be able to add/edit/delete positions in lists and mark them as complete.
  3. Lists should be shareable between several devices.

We won’t go into very sophisticated UI, we’ll try to use most of standard UI elements. Moreover, we’ll use some of iOS 6 features which will make our application better.

Let’s start with third requirement. We’ll cheat on that by using standard reminders as storage for our lists. If you use iCloud reminders, you can share every list with others, so we’ll not code “sharing”, we’ll suggest and guide our users to use system-provided features.
Actually, standard Reminders.app covers all of our required features (and even more). But goal itself looks good for the tutorial.

So, what will cover?

  1. EventKit – framework for accessing calendar events and reminders.
  2. State restoration (it was covered in earlier posts – part 1, part 2, part 3, all in Russian).


We’ll name our application iRemember. Source code is available on GitHub. I’ll skip most of obvious steps, trying to focus on important details.

Let’s start with creating reminder manager class, which will be responsible for accessing and creating reminders. API of our class will look like this.

typedef void (^IRReminderFetchCompletionBlock)(NSArray *reminders);
typedef void (^IRReminderAddCompletionBlock)(EKReminder *reminder);
typedef void (^IRReminderOperationCompletionBlock)(BOOL result);

@interface IRReminderManager : NSObject

// singleton

+(IRReminderManager*)defaultManager;


// Access to reminders

@property (atomic, readonly) BOOL accessGranted;
-(void)requestAccess;
-(void)requestAccessAndWait;


// Verification methods

-(BOOL)isCalendarIdentifierValid:(NSString*)calendarIdentifier;


// Fetch and add reminders

-(void)fetchRemindersInCalendarWithIdentifier:(NSString*)identifier 
                                   completion:(IRReminderFetchCompletionBlock)completionBlock;
-(void)addReminderWithTitle:(NSString*)title 
   inCalendarWithIdentifier:(NSString*)calendarIdentifier
                 completion:(IRReminderAddCompletionBlock)completionBlock;
-(void)removeReminder:(EKReminder*)reminder
       withCompletion:(IRReminderOperationCompletionBlock)completionBlock;


// Sources, calendars

-(NSArray*)sources;
-(NSArray*)reminderCalendars;
-(EKCalendar*)addCalendarWithTitle:(NSString*)title 
            inSourceWithIdentifier:(NSString*)sourceIdentifier;

@end

Our manager class uses singleton pattern. All requests will go through a single object. We’ll use typical implementation provided by GCD.

+(IRReminderManager*)defaultManager
{
    static dispatch_once_t onceToken;
    static IRReminderManager *manager;
    dispatch_once(&onceToken, ^{
        manager = [[IRReminderManager alloc] init];
    });
    return manager;
}

Main important thing about using reminders in iOS – you have to request access. User will be prompted first time application is launched. Later on, user can disable and enable access to reminders by your application. So, access should be requested and response is fired asynchronously. We have to be aware of this and prevent user from using application when access to reminders is revoked. We’ll take a note of this.

Now, let’s focus on getting the information. EventKit uses several important entities (we’ll focus only on reminders part):

  1. EKEventStore – event store, our accessor to database, all requests go through event store.
  2. EKSource – account which is used to store calendars (for example, iCloud account, or Exchange account, or local database), event store could access to all configured accounts.
  3. EKCalendar – calendar which stores reminders.
  4. EKReminder – actual reminder entry.

Let’s create method to get list of all calendars (or “Lists”).

-(NSArray*)reminderCalendars
{
    return self.accessGranted ? [self.store calendarsForEntityType:EKEntityTypeReminder] : nil;
}

This method synchronously returns list of calendars. Each EKCalendar entry has reference to its EKSource and, most important, calendarIdentifier property (which is NSString). This identifier will be used to pass reference between our view controllers.

Next, let’s create method that will load all incomplete reminders from specific calendar.

-(void)fetchRemindersInCalendarWithIdentifier:(NSString*)calendarIdentifier 
                                   completion:(IRReminderFetchCompletionBlock)completionBlock
{
    if (self.accessGranted)
    {
        EKCalendar *calendar = [self.store calendarWithIdentifier:calendarIdentifier];
        NSPredicate *predicate = [self.store predicateForIncompleteRemindersWithDueDateStarting:nil
                                                                                         ending:nil
                                                                                      calendars:@[calendar]];
        [self.store fetchRemindersMatchingPredicate:predicate completion:completionBlock];
    }
    else if (completionBlock)
    {
        completionBlock(nil);
    }
}

This method works asynchronously. We’re passing calendarIdentifier as a parameter, and by using EKEventStore method we get EKCalendar object.
It uses NSPredicate for fetching reminders. EKEventStore provides methods for most used predicates. We use -predicateForIncompleteRemindersWithDueDateStarting:ending:calendars: method.
Fetch process takes time, so results are not returned immediately, completion block is called when results are ready. Block gets array of EKReminder objects.

That’s all for now, we’ll continue in next posts.

For more information, consult Apple documentation – Calendar and Reminders Programming Guide, Event Kit Framework Reference.
Again, source code for this tutorial is available on GitHub.

Advertisements

Leave a Reply

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

WordPress.com Logo

You are commenting using your WordPress.com 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 )

Google+ photo

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

Connecting to %s