EventKitのリマインダーを使う方法

EventKitからリマインダーを使う方法のメモです。メモメモ。

iOS6からはEventKitにリマインダーを使うためのAPIが追加されました。ので、iOS6以上対象です。いまさらiOS5ってこともないと思いますけど。

カレンダーのイベントを触ったことがある人は、ほとんど同じように使えるのであまり戸惑うこともないと思います。正直簡単です。

とりあえず最初にやることはプロジェクトに `EventKit.framework` を追加して `#import <EventKit/EventKit.h>` すること。

## リマインダーのデータを読む前に

リマインダーのデータはプライバシーにより守られているので、まずは読み取りの許可を取らないといけません。まあ色々やりかたはあると思いますが、僕は以下のようにしています。ココらへんは沢山情報あるので詳しいことは割愛。写真とかカレンダーと一緒。

```objc
if([EKEventStore authorizationStatusForEntityType:EKEntityTypeReminder] == EKAuthorizationStatusAuthorized) {
   // リマインダーにアクセスできる場合
} else {
   EKEventStore *eventStore = [[EKEventStore alloc] init];
   [eventStore requestAccessToEntityType:EKEntityTypeReminder
         completion:^(BOOL granted, NSError *error) {
            if(granted) {
               // はじめてリマインダーにアクセスする場合にアラートが表示されて、OKした場合にここにくるよ
            } else {
               dispatch_async(dispatch_get_main_queue(), ^{
                     // アクセス権がありません。
                     // "プライバシー設定"でアクセスを有効にできます。
                     UIAlertView *alert = [[UIAlertView alloc]
                           initWithTitle:NSLocalizedString(@"This app does not have access to you reminders.", nil)
                                 message:NSLocalizedString(@"To display your reminder, enable [YOUR APP] in the \"Privacy\" → \"Reminders\" in the Settings.app.", nil)
                                delegate:nil
                       cancelButtonTitle:NSLocalizedString(@"OK", nil)
                       otherButtonTitles:nil];
                     [alert show];
                 });
            }
         }
   ];
}
```

## リマインダーの書き込み

シミュレータにはリマインダーアプリが入ってないので、サンプルデータを作る場合に自分のアプリで登録しないといけません。地味にめんどくさいです。

```objc
EKEventStore *eventStore = [[EKEventStore alloc] init];
EKReminder *reminder = [EKReminder reminderWithEventStore:eventStore];
reminder.title = @"サンプルリマインダー";
reminder.calendar = [eventStore defaultCalendarForNewReminders];

// 期限が必要な場合
NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
[dateComponents setYear:2013];
[dateComponents setMonth:12];
[dateComponents setDay:18];
[dateComponents setHour:20];
reminder.dueDateComponents = dateComponents;

// 通知を追加
[reminder addAlarm:[EKAlarm alarmWithAbsoluteDate:[[NSCalendar currentCalendar] dateFromComponents:dateComponents]]];

NSError *error;
if(![eventStore saveReminder:reminder commit:YES error:&error]) NSLog(@"%@", error);
```
calendarプロパティに入るのはリマインダーのリストのことなのですが、ここではとりあえずデフォルトのリストを使っています。ちなみに設定アプリの「リマインダー＞デフォルトのリスト」で変更できます。シミュレータにはそういう項目が無いので変更できません。

dueDateComponentsがリマインダーの期限です。addAlarmで入れたEKAlarmが通知になります。addAlarmを入れないと通知されませんので気をつけましょう。 **dueDateComponentsを入れたらaddAlarmも入れた方がいいと思います。** リマインダーのデータを読む際には「dueDateComponents」が入っているかいないかで検索できるのに対して、alarmの有無は考慮されていないからです。

ほかにも、リマインダーアプリを開いた際にリストに表示される日時は「dueDateComponents」の値ですが、alarmsが空っぽだと編集画面で編集できなかったりとややこしいことになります。このへんの事は後述する小ネタに書いたリンク先にとりとめもなく書いてあります。

## リマインダーのリスト

リマインダーアプリでは「リスト」という名前でリマインダーを分類しているのでそれを取得してみます。先ほど出てきたEKReminderのcalendarプロパティに入れたり、特定のリストから検索する際に使用します。リマインダーなのにカレンダーとはややこしいですね。そのうちしれっとdeprecatedになりそう。

登録済みのリストは、以下のメソッドで取得できます。

```objc
EKEventStore *eventStore = [[EKEventStore alloc] init];
NSArray *lists = [eventStore calendarsForEntityType:EKEntityTypeReminder];
EKCalendar *sampleList = [lists firstObject];
```

このように、EKCalendarというオブジェクトの配列が取得できます。この中から必要なものをユーザに選択させたりして特定のリストを抜き出します。EKCalendarにcalendarIdentifierというプロパティがあるので、選択内容を保存しておく場合はこれを使います。

```objc
EKCalendar *sampleList = [eventStore calendarWithIdentifier:savedIdentifier]
```

新しいリストもプログラムから追加できます。

```objc
EKCalendar *newList = [EKCalendar calendarForEntityType:EKEntityTypeReminder eventStore:eventStore];
newList.title = @"新しいリスト";

NSError *error;
if(![eventStore saveCalendar:newList commit:YES error:&error]) NSLog(@"%@", error);
```

## リマインダーのデータを読む

検索用のNSPredicateを作って、fetchRemindersMatchingPredicateを実行すると該当するリマインダーが返ってきます。NSPredicateの生成は以下の3種類のメソッドのうち、必要なものを使います。

```objc
- (NSPredicate *)predicateForRemindersInCalendars:(NSArray *)calendars
- (NSPredicate *)predicateForIncompleteRemindersWithDueDateStarting:(NSDate *)startDate ending:(NSDate *)endDate calendars:(NSArray *)calendars
- (NSPredicate *)predicateForIncompleteRemindersWithDueDateStarting:(NSDate *)startDate ending:(NSDate *)endDate calendars:(NSArray *)calendars
```

一番上はリマインダー全部で下の二つはdueDateComponentsが指定の範囲内のもので完了(completed)のYES:NOで絞り込みます。まあ二番目のやつが一番使うんじゃないでしょうか。

```objc
EKEventStore *eventStore = [[EKEventStore alloc] init];
NSDate *startDate = [NSDate distantPast];
NSDate *endDate   = [NSDate distantFuture];
NSPredicate *predicate = [eventStore predicateForIncompleteRemindersWithDueDateStarting:startDate ending:endDate calendars:nil];
[eventStore fetchRemindersMatchingPredicate:predicate completion:^(NSArray *reminders) {
   // 非同期でフェッチしてくれて便利
}];
```

条件に該当するEKReminderオブジェクトの配列が返ってきます。

内容を変更したい場合は、新規作成時と同じようにプロパティを書き換えてsaveするのが宜しいかと。

```objc
EKReminder *reminder = [reminders firstObject];
reminder.completed = YES;
NSError *error;
if(![eventStore saveReminder:reminder commit:YES error:&error]) NSLog(@"%@", error);
```

## 小ネタ

この記事を書くにあたって色々調べてわかったことを [EKReminderのメモ](http://iphone-dev.g.hatena.ne.jp/ktakayama/20131213/1386929951)  として書きましたのでお暇な時にでもどうぞ。結構まとまりがない感じに書きなぐったので読みにくいかもしれませんけど。

リマインダーがall-dayイベントになることについてとか、個人的に興味深かった。