🎃

Swiftでカレンダーのイベントを取得

2022/05/31に公開

iOSやmacOSのカレンダーに入力している予定をアプリから読み込む手順。

予定を読み込むにはユーザの許可が必要

カレンダーの予定はアプリが勝手に読み込むことはできない。写真を読み込む場合のようにユーザの許可が必要になる。

Privacy - Calendars Usage Description

というわけでいつもの様にまずはinfo.plistに読み込みが必要だという情報を入れる必要がある。
カレンダーの予定の読み込みに必要なのはPrivacy - Calendars Usage Description。TypeはStringで値として読み込みが必要な理由を記述する。

今回は簡単に下記の様にしてみた。

状態を調べてユーザに許可を得る

次はカレンダーの予定の読み込みを許可されているかどうかを確認して、許可されていない場合は許可を得るダイアログを表示するコードを書く。

まずは最初にEKEventStoreクラスのインスタンスを作成しておく。これを使って許可を得たりイベントの一覧を取得したりするs。

EventManager.swift
        self.eventStore = EKEventStore()

次は読み込みが許可されているかどうかを確認するコード。

EventManager.swift
    func authorizationStatus() -> Bool {
        let status = EKEventStore.authorizationStatus(for: .event)
        
        switch status {
        case .authorized:
            print("Authorized")
            return true
        case .notDetermined:
            print("Not determined")
            return false
        case .restricted:
            print("Restricted")
            return false
        case .denied:
            print("Denied")
            return false
        @unknown default:
            print("Unknown default")
            return false
        }
    }

この例ではcaseを全部書いているが.authorizedだけ確認すれば問題ない。(Xcodeが全部書いてくれるのでそのまま使ってる。更に@unknown defaultもワーニングでFixすると入れてくれる!)

上記コードで許可されていない(falseが返ってきた)場合は下記コードで許可を得るダイアログを表示する。

EventManager.swift
            eventStore.requestAccess(to: .event, completion: { granted, error in
                if granted {
                    print("allowed now")
                    return
                }
                else {
                    print("Not allowed")
                }
            })

ちなみに上記コードでダイアログを表示してユーザが拒否をした場合、一度アプリを終了して再度アプリを起動してもダイアログは表示されなくなるので注意。拒否しているのに毎回ダイアログを表示されるのはユーザとしても鬱陶しいだろう…。

以上でカレンダーの予定を読み込む準備は終了。

カレンダーの予定を読み込む

上記で許可を得る時に使ったEKEventStoreを使ってカレンダーを読み込む。許可を取る時に使ったものと違うEKEventStoreを使うと読み込めないので注意。(違ってもそちらでも許可を得れていれば大丈夫だろうけど確認はしていない)

読み込みはEKEventStoreevents(matching: NSPredicate)を使って読み込む。
ここで指定するNSPredicateは検索条件。EKEventStore.predicateForEventsで作成できる。

EventManager.swift
        let calendars = eventStore.calendars(for: .event)
        let predicate = eventStore.predicateForEvents(withStart: Calendar.current.date(byAdding: .month, value: -3, to: Date())!, end: Date(), calendars: calendars)

上記の例だと日付の範囲をstartDateendDateを使って指定、更に検索をするカレンダーを指定して予定を読み込んでいる。またカレンダーはeventStore.calendars(for: .event)で全てのカレンダーを指定しているが、検索するカレンダーを指定したい場合は必要なカレンダーの配列を作ってcalendarsに渡せば良い。

ちなみに、考えると当たり前のことだがstartDateendDateより前の日付になる。「今日から3ヶ月前まで」と考えてstartDateを今日、endDate-3にしても何も読み込めないので注意。(自分はちょっとハマった…)

新規イベントを作成する

必要だったのはイベントを取得することだけだったのだが、せっかくなので新規イベントを作成するコードも書いてみた。

EventManager.swift
    func newEvent(title: String) {
        let event = EKEvent(eventStore: eventStore)
        event.title = title
        event.startDate = Date()
        event.endDate = Calendar.current.date(byAdding: .minute, value: 30, to: Date())!
        event.calendar = eventStore.defaultCalendarForNewEvents
        try! eventStore.save(event, span: .thisEvent)

ここでもやはりEKEventStoreは許可を得たものを使用する。

イベントを削除する

ここまで来たのでイベントを削除するコードも。

EventManager.swift
        try! eventStore.remove(event, span: .thisEvent)

remove(_,span:)に削除したいイベントを渡すだけ。spanは芋づる式にイベントが繋がっていなければ.thisEventで問題ない。

サンプル

簡単なサンプルを作成してみた。
起動するとユーザの許可を得て3ヶ月前までの予定をリスト表示するだけのもの。

New Eventボタンをタップすると新しく30分間のイベントを作成する。
また、イベントをスワイプして削除することも可能。

https://github.com/paraches/EventKitSample

最後に

カレンダーから予定を取得するのは非常に簡単。なのでサンプル書き始めてつい新しいイベントの作成と削除も加えてしまった。

が、イベントの追加と削除はエラーハンドリングをしっかりやらないとトラブルの元になる。サンプルでは!で落としているがそうしたくない場合には失敗したことを返してテーブルビューのセルを削除しないようにする必要がある。

というわけで、カレンダーの予定を扱うのは簡単で良いのだが、日付に絡むコードは本当に面倒。タイムゾーンとか年号とか止めてよ!ってなる…

Discussion