きりかノート 3冊め

おあそびプログラミング

発表:リマインダーの繰り返しを自由に設定する (Mac)(2/08)

アプリで設定できるものでは足らないので、EventKit経由で自由に繰り返しを設定するよ、という話。

あらすじ

  • リマインダーで「3週間ごとの繰り返し」を設定したい
  • それEventKitでできるよ
  • 実際にAPI操作して確認してみた。ほんとうにできるぜ!
  • RubyCocoaの宣伝

リマインダー

OS X/iOSに付属するAppleが提供するアプリで、

  • 指定の期日にアラームを鳴らしたり通知を表示させたりできる。
  • iCloudiTunes経由で複数のデバイスで同期できる。
  • 一定期間ごとの繰り返しを指定できる。

というもの。この繰り返しが、アプリ上では

  • 毎日
  • 毎週
  • 隔週(2週間)
  • 毎月
  • 毎年

の5種類のうちから選ぶようになっていて、それ以外の期間が設定できない。もしかしたらそういう操作があるのかもしれないけど、みつからなかった。

EventKitとリマインダー

以前の勉強会で他の人がEventKitの発表をしていて、「カレンダーやリマインダーを操作できる」という話があったのを思い出したので、それを試してみることに。

Mac Developer Libraryの"Calendar and Reminders Programming Guide "をざっと読むと、"EKRecurrenceRule"が繰り返しをあらわすクラスのようだ。EKRecurrenceRuleのプロパティでは、繰り返し期間は

  • frequency: enum EKRecurrenceFrequencyであらわされる{年,月,週,日}の単位
  • intercal: 期間の数。数値。

の2つの組み合わせで表現されており、任意の期間が指定できそうだ。また、

  • daysOfTheWeek: 一週間のうちの曜日。複数指定もあり。

などずいぶん込み入った繰り返しも表現可能なようだ。

実際のところAPI上は可能でも、その繰り返しを受け入れるか、その繰り返しで次回の期限が設定されるか、はわからないところ。実際に試して確認したほうがよさそう。

動作確認の前にちょっとEventKitの構成を簡単に説明。リマインダーまわりは次のような構造になっている。

  • EKEventStore - リマインダー or イベントを保存しているもの。
  • EKCalendar - "ビジネス"、"買い物"などの種類。
  • EKReminder - 個々のリマインダー。繰り返しの情報はこいつが持っている。

ほぼアプリみたままね。

実際に繰り返しを持ったリマインダーを登録してみる

いちいちアプリをつくるのもめんどうなので、RubyCocoairbで直接操作することに。以下がデモのあんちょこ。実行時に「ターミナル.app」にリマインダーへのアクセス許可が必要なことに注意。(デモのとき無効にしてたの忘れてて、あれっ?となってしまった。。)

   require 'osx/cocoa'
   include OSX
   require_framework 'EventKit'

   # リマインダーのストア
   store = EKEventStore.alloc.initWithAccessToEntityTypes(EKEntityTypeReminder)
   # 追加するリマインダーをタイトルからとってくる
   calendars = store.calendarsForEntityType(EKEntityTypeReminder)
   calendar = calendars.find {|cal| cal.title == "テストだよ!"} # 環境によってタイトルは要調整

   # 新しいリマインダーを作成する
   reminder = EKReminder.reminderWithEventStore(store)
   reminder.title = "3週間ごとに実行する!"

   # 期限の日付を用意する
   due_date = NSDateComponents.alloc.init
   due_date.year = 2014; due_date.month = 2; due_date.day = 8
   due_date.hour = 18; due_date.minute = 30
   reminder.dueDateComponents = due_date

   # 繰り返しルール(3週間ごと)を作成する
   rule = EKRecurrenceRule.alloc.initRecurrenceWithFrequency_interval_end(EKRecurrenceFrequencyWeekly, 3, nil)
   reminder.addRecurrenceRule(rule)

   # 保存する
   result, err = store.saveReminder_commit_error(reminder, true)
   # おおっとエラー。メッセージをみてみましょう。
   err.localizedDescription

   # カレンダーの指定を忘れてましたね
   reminder.calendar = calendar
   result, err = store.saveReminder_commit_error(reminder, true)

   ## => リマインダーに保存されていること、指定の3週間ごとに実行されることを確認

これで作成したリマインダーを「完了」→「次の期限を確認」として、実際に3週間ごとの繰り返しで動作していることを確認した。めでたしめでたし。

もっと複雑な繰り返しを試してみる

"月2回の締め日(15, 月末)"。daysOfTheMonthでは負の値は末日からの日にちになるので、"-1"は月末になる。

   rule1 = EKRecurrenceRule.alloc.initRecurrenceWithFrequency_\
               interval_daysOfTheWeek_daysOfTheMonth_monthsOfTheYear_\
               weeksOfTheYear_daysOfTheYear_setPositions_end_(EKRecurrenceFrequencyMonthly,
                   1, nil, [15, -1], nil,
                   nil, nil, nil, nil)

"毎月第1、第3水曜日"。EKRecurrenceDayOfWeekを使うと「指定期間のn番目の指定曜日」が指定できる。次のようにすると、月々の第1水曜と第3水曜を対象にできる。

   days_of_week = [EKRecurrenceDayOfWeek.dayOfWeek_weekNumber(4, 1),
                   EKRecurrenceDayOfWeek.dayOfWeek_weekNumber(4, 3)]
   rule2 = EKRecurrenceRule.alloc.initRecurrenceWithFrequency_\
               interval_daysOfTheWeek_daysOfTheMonth_monthsOfTheYear_\
               weeksOfTheYear_daysOfTheYear_setPositions_end_(EKRecurrenceFrequencyMonthly,
                   1, days_of_week, nil, nil,
                   nil, nil, nil, nil)

それぞれ上記の繰り返しを適用したリマインダーを作成し、実際に意図したとおりに動作することを確認した。

まとめ

EventKitのEKRecurrenceRuleではかなり自由に繰り返し期間を設定できる。ただアプリ上は「カスタムの期間」となってしまい、登録後に設定がよくわからなくなってしまうのがちょっと残念。

RubyCocoaを使うと、アプリを作成しなくてもAPIの動作確認できて便利!(宣伝)。

RubyCocoaについて補足など

Mavericksでは"ruby"は2.0になっているので、今回と同様の確認をするには"/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/irb"を呼び出す必要があるので注意。現在、鋭意2.x対応中なので、自分のやる気を高めるためにも「まだ使えるポイントはあるんだぜ」アピールをしてきた。