Archive for the 'EKEventStore' Category

2015/09/25 Remindersのイベントに場所(geofence alarm)を追加する(OS X 10.11版)

Reminders.app(日本語名:リマインダー)の指定イベントに場所(geofence alarm)を追加するAppleScriptのEl Capitan(OS X 10.11)版です。

Yosemite(OS X 10.10.x)のScript Editor上では実行はおろか構文確認(コンパイル)もできないのでご注意ください。

El Capitanのリリースノートで「バグ修正」項目として紹介されている内容の1つで・・・Cocoaの各APIでの定数をAppleScriptにBridgeするようになった、という項目のテスト用にOS X 10.11上で一部修正してみました。

OS X 10.10.x上では、こうした定数があったら、

enums.png

最初の項目が0で、次が1で・・・とあたりをつけて数値で指定するとか、Xcode上でヘッダーファイルを確認して実際の値を調べる・・・という不毛な作業が必要だったわけですが、それがEl Capitanでは解消されるということです。

ただ、目下AppleScriptのプログラムリストの中で、「変数およびサブルーチン名」として色分けされる項目が、とくにCocoaの機能を使うようになってからというもの増えすぎており、可読性が落ちているので、なんとかしてほしいところです。

ちなみに、ASObjC Explorer 4ではこの問題を軽減しており、「サブルーチン名」と「Cocoaのメソッド名」については変数名とは色分けするようになっています(El Capitan上で比較のためにスクリーンショットを撮りましたが、掲載してはいけないことに気づいて削除)。

AppleScript名:Remindersのイベントに場所(geofence alarm)を追加する_10.11
– Created 2014-12-08 by Shane Stanley
– Modified 2015-09-24 by Takaaki Naganoya –Geofence Alarm
– Modified 2015-09-24 by Shane Stanley –Fix the way to Create the geofence alarm
– Modified 2015-09-24 by Takaaki Naganoya –change and test for El Capitan’s Enum bridging

–Reference:
–http://stackoverflow.com/questions/26903847/add-location-to-ekevent-ios-calendar
–http://timhibbard.com/blog/2013/01/03/how-to-create-remove-and-manage-geofence-reminders-in-ios-programmatically-with-xcode/

use AppleScript version “2.5″
use scripting additions
use framework “Foundation”
use framework “EventKit”

–Fetch an event to add geofence alarm
tell application “Reminders”
  tell account “iCloud”
    tell list “ホーム” –Use “Home” in English environment or your favorite one
      set theID to id of (last reminder whose completed is false)
    end tell
  end tell
end tell

set theID to text 20 thru -1 of theID

setLocationToLeaveForReminderID(35.745769, 139.675565, “ゲームシティ板橋”, theID, 200)

–指定場所に到着した時に発動するGeofence Alarmを指定のリマインダーに登録する。Geofence半径指定つき(単位:メートル)
on setLocationToEnterForReminderID(aLatitude, aLongitude, aTitle, theID, aRangeByMeter)
  – Get event store
  
set eventStore to current application’s EKEventStore’s alloc()’s initWithAccessToEntityTypes:(current application’s EKEntityMaskReminder)
  
– Get the reminder
  
set theReminder to eventStore’s calendarItemWithIdentifier:theID
  
–Create the geofence alarm
  
set enterAlarm to current application’s EKAlarm’s alarmWithRelativeOffset:0
  
enterAlarm’s setProximity:(current application’s EKAlarmProximityEnter)
  
set structLoc to current application’s EKStructuredLocation’s locationWithTitle:aTitle
  
set aLoc to current application’s CLLocation’s alloc()’s initWithLatitude:aLatitude longitude:aLongitude
  
structLoc’s setGeoLocation:aLoc
  
  
– Set radius by meters
  
structLoc’s setRadius:aRangeByMeter
  
enterAlarm’s setStructuredLocation:structLoc
  
  
theReminder’s addAlarm:enterAlarm
  
set aRes to eventStore’s saveReminder:theReminder commit:true |error|:(missing value)
  
  
return aRes as boolean
end setLocationToEnterForReminderID

–指定場所を出発した時に発動するGeofence Alarmを指定のリマインダーに登録する。Geofence半径指定つき(単位:メートル)
on setLocationToLeaveForReminderID(aLatitude, aLongitude, aTitle, theID, aRangeByMeter)
  – Get event store; 1 means reminders
  
set eventStore to current application’s EKEventStore’s alloc()’s initWithAccessToEntityTypes:(current application’s EKEntityMaskReminder)
  
– Get the reminder
  
set theReminder to eventStore’s calendarItemWithIdentifier:theID
  
–Create the geofence alarm
  
set leaveAlarm to current application’s EKAlarm’s alarmWithRelativeOffset:0
  
leaveAlarm’s setProximity:(current application’s EKAlarmProximityLeave)
  
set structLoc to current application’s EKStructuredLocation’s locationWithTitle:aTitle
  
set aLoc to current application’s CLLocation’s alloc()’s initWithLatitude:aLatitude longitude:aLongitude
  
structLoc’s setGeoLocation:aLoc
  
  
– Set radius by meters
  
structLoc’s setRadius:aRangeByMeter
  
leaveAlarm’s setStructuredLocation:structLoc
  
  
theReminder’s addAlarm:leaveAlarm
  
set aRes to eventStore’s saveReminder:theReminder commit:true |error|:(missing value)
  
  
return aRes as boolean
end setLocationToLeaveForReminderID

★Click Here to Open This Script 

2015/09/24 Remindersのイベントに場所(geofence alarm)を追加する

Reminders.app(日本語名:リマインダー)の指定イベントに場所(geofence alarm)を追加するAppleScriptです。

リマインダーのAppleScript用語辞書では「場所」についてはまったくサポート機能が用意されておらず(El CapitanのRemindersでもサポートなし)、全世界のScripterが肩を落としたものでした(Apple純正アプリのAppleScript用語辞書の残念さは本当に残念です。Finderのタグなど登場して相当の時間が経っているのに機能が追加されていません。タグについてはASOCで直接操作できるので、Appleの対応をもはや期待していない昨今)。

rem5.png
▲リマインダー(Reminders.app)のAppleScript用語辞書にはLocation関連の機能はない

そこで、出来のよくないリマインダーをアテにせず、Cocoaの機能を呼び出して、AppleScriptだけで「場所」の情報を追加するAppleScriptを書いてみました(Shane Stanleyの添削済み)。

この「場所」については、若干の説明が必要です。

geofence.png

緯度・経度でピンポイントの場所を指定できますが、ピンポイントすぎるのと誤差を許容するため「だいたいこのあたり」といった範囲の指定が必要になります。これを「Geofence」と呼ぶようです。

このピンポイントの場所から「半径XXXXメートル以内」のGeofenceに入った場合のアラーム(Enter Alarm)と、出た場合のアラーム(Leave Alarm)の2種類のうちどちらかを設定できるとのこと。

逆に、入るとか出るとかいうアクション指定のないものも作れますが、これがどういう挙動をするのかいまひとつ分かりません(Geofence内でずっと何かの通知が出る?)。

rem1.png
▲テスト用に作ったリマインダー項目

rem2.png
▲初期状態では何も「場所」関連の情報はついていません

rem3.png
▲本Script実行後の状態

rem4.png
▲内容を確認すると、意図したとおり「ゲームシティ板橋」のGeofenceが追加

img_2822_resized.png
▲ゲームシティ板橋店

AppleScript名:Remindersのイベントに場所(geofence alarm)を追加する
– Created 2014-12-08 by Shane Stanley
– Modified 2015-09-24 by Takaaki Naganoya
– Modified 2015-09-24 by Shane Stanley

–Reference:
–http://stackoverflow.com/questions/26903847/add-location-to-ekevent-ios-calendar
–http://timhibbard.com/blog/2013/01/03/how-to-create-remove-and-manage-geofence-reminders-in-ios-programmatically-with-xcode/

use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “EventKit”

–Fetch an event to add geofence alarm
tell application “Reminders”
  tell account “iCloud”
    tell list “ホーム” –Use “Home” in English environment or your favorite one
      set theID to id of (last reminder whose completed is false)
      
–>  ”x-apple-reminder://868467FD-1F90-47EF-A9B1-A39334341D41″
    end tell
  end tell
end tell

set theID to text 20 thru -1 of theID
–> “868467FD-1F90-47EF-A9B1-A39334341D41″

setLocationToEnterForReminderID(35.745769, 139.675565, “ゲームシティ板橋”, theID, 200)

on setLocationToEnterForReminderID(aLatitude, aLongitude, aTitle, theID, aRangeByMeter)
  – Get event store; 1 means reminders
  
set eventStore to current application’s EKEventStore’s alloc()’s initWithAccessToEntityTypes:2 –Deprecated in OS X 10.9 ?
  
(* Enums: { EKEntityMaskEvent:1, EKEntityMaskReminder:2} *)
  
  
– Get the reminder
  
set theReminder to eventStore’s calendarItemWithIdentifier:theID
  
  
–Create the geofence alarm
  
set enterAlarm to current application’s EKAlarm’s alarmWithRelativeOffset:0 –ここがキモ
  
  
enterAlarm’s setProximity:1 – EKAlarmProximityEnter
  
(* Enums: { EKAlarmProximityNone:0, EKAlarmProximityEnter:1, EKAlarmProximityLeave:2 } *)
  
  
set structLoc to current application’s EKStructuredLocation’s locationWithTitle:aTitle
  
set aLoc to current application’s CLLocation’s alloc()’s initWithLatitude:aLatitude longitude:aLongitude
  
structLoc’s setGeoLocation:aLoc
  
  
– Set radius by meters
  
structLoc’s setRadius:aRangeByMeter –by meters
  
enterAlarm’s setStructuredLocation:structLoc
  
  
theReminder’s addAlarm:enterAlarm
  
set aRes to eventStore’s saveReminder:theReminder commit:true |error|:(missing value)
  
  
return aRes as boolean
end setLocationToEnterForReminderID

on setLocationToLeaveForReminderID(aLatitude, aLongitude, aTitle, theID, aRangeByMeter)
  – Get event store; 1 means reminders
  
set eventStore to current application’s EKEventStore’s alloc()’s initWithAccessToEntityTypes:2 –Deprecated in OS X 10.9 ?
  
(* Enums: { EKEntityMaskEvent:1, EKEntityMaskReminder:2} *)
  
  
– Get the reminder
  
set theReminder to eventStore’s calendarItemWithIdentifier:theID
  
  
–Create the geofence alarm
  
set enterAlarm to current application’s EKAlarm’s alarmWithRelativeOffset:0 –ここがキモ
  
  
enterAlarm’s setProximity:2 – EKAlarmProximityLeave
  
(* Enums: { EKAlarmProximityNone:0, EKAlarmProximityEnter:1, EKAlarmProximityLeave:2 } *)
  
  
set structLoc to current application’s EKStructuredLocation’s locationWithTitle:aTitle
  
set aLoc to current application’s CLLocation’s alloc()’s initWithLatitude:aLatitude longitude:aLongitude
  
structLoc’s setGeoLocation:aLoc
  
  
– Set radius by meters
  
structLoc’s setRadius:aRangeByMeter –by meters
  
enterAlarm’s setStructuredLocation:structLoc
  
  
theReminder’s addAlarm:enterAlarm
  
set aRes to eventStore’s saveReminder:theReminder commit:true |error|:(missing value)
  
  
return aRes as boolean
end setLocationToLeaveForReminderID

★Click Here to Open This Script 

2014/12/08 Remindersのイベントの期日を追加する

リマインダー(Reminders.app)の特定のreminderに対して、「期日」を追加するAppleScriptです。

Twitter上で、reminderに「場所」を追加できないだろうかという問いがあって・・・AppleScriptでは、リマインダー(Reminder.app)のAppleScript用語辞書には「場所」の予約語がないので、そのままでは手も足も出ません。ただ、ASOC経由でCocoaのEventKitにアクセスすれば、できそうな雰囲気がありました。

US AppleのAppleScript Users MLの過去ログをあさってみたら、きわめて近い内容のものがあったので、実際に試してみたら・・・OS X 10.10.1上ではエラーに。

ML上でShane Stanleyに聞いてみたら「10.10上では少々直す必要がある」とのことで、その場で教えてもらえました。ドキュメントを読めば分かる的な話ではあったのですが、Appleのオンラインドキュメントには定数の値が書かれていません(自分はここでスタックしていました)。

・・・どうやらShaneのいう「ドキュメント」とは「ヘッダーファイル」のことだったらしいことに後から気づきました。

# Script Editor上で編集中のAppleScriptのuse文をスキャンして、どのヘッダーファイルをオープンするかをユーザーに尋ねてオープンするAppleScriptを書いておく必要がありそう、、、

とりあえず、本Scriptでは「場所」ではなく「期限」を追加します。さらに調査を行えば、「場所」の追加もできそうですが・・・。

eventkit1.png

iCloud上の「ホーム」というリストにある(最新の)reminderに対して、「期限」を追加します。

eventkit2.png

AppleScript名:Remindersのイベントの期日を追加する
– Created 2014-03-08 by Shane Stanley
– Fixed 2014-12-08 by Shane Stanley
use AppleScript version “2.4″
use scripting additions
use framework “Foundation”
use framework “ASObjCExtras”
use framework “EventKit”

–とりあえず、1つのreminderのIDを取得
tell application “Reminders”
  tell account “iCloud”
    tell list “ホーム”
      set theID to id of (last reminder whose completed is false)
    end tell
  end tell
end tell

set theID to text 20 thru -1 of theID
–> “x-apple-reminder://B63F627B-A177-4CA7-9811-2AEB4875B407″
setDueDate_forReminderID_(((current date) + (2 * days)), theID)

on setDueDate:ASDate forReminderID:theID
  
  
– ASのdateをNSDateに変換
  
set theNSDate to my makeNSDateFrom:ASDate
  
  
– Cocoaのカレンダーを取得
  
set theCalendar to current application’s NSCalendar’s currentCalendar()
  
  
– Components of dateを取得
  
set theComponents to theCalendar’s componentsInTimeZone:(current application’s NSTimeZone’s localTimeZone()) fromDate:theNSDate
  
  
– get event store; 1 means reminders –> 2
  
set eventStore to current application’s EKEventStore’s alloc()’s initWithAccessToEntityTypes:2
  
  
– get the reminder
  
set theReminder to eventStore’s calendarItemWithIdentifier:theID
  
  
– 「期限」の日付を設定する
  
theReminder’s setDueDateComponents:theComponents
  
  
– 保存
  
eventStore’s saveReminder:theReminder commit:true |error|:(missing value)
  
end setDueDate:forReminderID:

on makeNSDateFrom:theASDate
  
  
set {theYear, theMonth, theDay, theSeconds} to theASDate’s {year, month, day, time}
  
  
if theYear < 0 then
    set theYear to -theYear
    
set theEra to 0
  else
    set theEra to 1
  end if
  
  
set theCalendar to current application’s NSCalendar’s currentCalendar()
  
set newDate to theCalendar’s dateWithEra:theEra |year|:theYear |month|:(theMonth as integer) ¬
    |day|:theDay hour:0 minute:0 |second|:theSeconds nanosecond:0
  
  
return newDate
  
end makeNSDateFrom:

★Click Here to Open This Script