WWDC Quick Look 💓 By SwiftGGTeam
Discover Calendar and EventKit

Discover Calendar and EventKit

元の動画を見る

ハイライト

iOS 17 では EKEventEditViewController が独立プロセスで動作し、カレンダー権限なしでイベントを追加可能。新しい write-only 権限でアプリは書き込みのみ可能。仮想会議拡張により、ビデオ通話アプリがカレンダーの場所選択に直接表示されます。

主要内容

カレンダー統合の 3 つの役割

アプリとカレンダーの統合には 3 つの一般的な役割があります:

  • イベント追加: 予約、チケット購入、待ち合わせ—確認情報をカレンダーに書き込む
  • イベント表示: カスタムカレンダーウィジェット、スケジュールプランナー
  • 双方向管理: 既存イベントの表示・編集、新しいカレンダーの作成

役割ごとに異なる権限要件と実装方法が必要です。

3 段階の権限レベル

iOS 17 ではカレンダー権限が 3 段階に分かれます:

  • 権限なし: EventKitUI または Siri Event Suggestions でイベント追加、権限不要
  • Write-only: カレンダーに直接書き込み可能だが既存イベントは読み取れない
  • Full access: 既存イベントの読み取り・変更・削除、新しいカレンダーの作成

権限リクエストは最小必要原則に従うべきです。イベント追加のみ必要なアプリの多くは EventKitUI で十分で、権限ダイアログは一切不要です。

EventKitUI: ゼロ権限でイベント追加

EKEventEditViewController は iOS 17 で独立プロセスで動作します。イベントを作成し属性を入力してエディタを表示し、ユーザーが確認するとイベントがカレンダーに直接保存されます。アプリはカレンダーデータに一切触れないため、権限リクエストは不要です。

最もシンプルで安全な統合方法です。ユーザーはエディタでカレンダーを選択し時間を変更でき、完全なコントロールを持ちます。

Siri Event Suggestions: 軽量な追加

レストラン予約、航空券、コンサートチケットなどに適しています。INReservation オブジェクトを作成し、INInteraction にラップしてシステムに donate します。イベントはカレンダーの受信トレイに表示され、ユーザーはワンタップで追加または無視できます。

権限ダイアログも UI も不要で、バックグラウンド自動推薦に適しています。

Write-only アクセス: カスタム UI シナリオ

カスタム編集インターフェース、一括イベント追加、サイレント追加が必要な場合は write-only 権限をリクエストします。iOS 17 では requestWriteOnlyAccessToEvents() が追加されました。

制限: 既存イベント(自分が追加したものを含む)の読み取り不可、カレンダーリストの読み取り不可、新しいカレンダーの作成不可。

Full Access: 慎重に使用

既存イベントの表示・更新・削除がコア機能に必要な場合のみリクエストします。Info.plist に NSCalendarsFullAccessUsageDescription を設定します。

full access 取得後は predicateForEvents でクエリ条件を作成し、events(matching:) で結果を取得します。返される配列は順序なしのため、自分でソートが必要です。

仮想会議拡張

ビデオ通話アプリは EKVirtualConferenceProvider を実装でき、カレンダーで場所を追加する際に会議オプションを直接表示できます。ユーザーが選択すると、カレンダーイベントにブランドアイコンと参加ボタンが表示され、タップでアプリに直接戻れます。

2 つのメソッドのみ実装: fetchAvailableRoomTypes() で会議室タイプを提供、fetchVirtualConference() で選択されたタイプの会議リンクと詳細を返す。

詳細

EventKitUI でイベントを追加

05:49

import EventKitUI

// event store を作成
let store = EKEventStore()

// イベントを作成して詳細を入力
let event = EKEvent(eventStore: store)
event.title = "WWDC23 Keynote"
let startDateComponents = DateComponents(year: 2023, month: 6, day: 5, hour: 10)
let startDate = Calendar.current.date(from: startDateComponents)!
event.startDate = startDate
event.endDate = Calendar.current.date(byAdding: .hour, value: 2, to: startDate)!
event.timeZone = TimeZone(identifier: "America/Los_Angeles")
event.location = "1 Apple Park Way, Cupertino, CA, United States"
event.notes = "Kick off an exhilarating week of technology and community."

// エディタビューコントローラを作成
let eventEditViewController = EKEventEditViewController()
eventEditViewController.event = event
eventEditViewController.eventStore = store
eventEditViewController.editViewDelegate = self

// エディタを表示
present(eventEditViewController, animated: true)

キーポイント:

  • EKEventEditViewController は iOS 17 で独立プロセスで動作
  • カレンダー権限は不要
  • ユーザーはエディタでイベント詳細の変更とカレンダー選択が可能
  • EKEventEditViewDelegate を実装するとユーザーが「追加」をタップしたかどうかを把握できる

Siri Event Suggestions

09:17

import Intents
import CoreLocation

// 予約参照を作成
let spokenPhrase = "Lunch at Caffè Macs"
let reservationReference = INSpeakableString(
    vocabularyIdentifier: "df9bc3f5",
    spokenPhrase: spokenPhrase,
    pronunciationHint: nil
)

// 時間範囲を設定
let duration = INDateComponentsRange(start: myEventStart, end: myEventEnd)

// 場所を設定
let location = CLPlacemark(
    location: myCLLocation,
    name: "Caffè Macs",
    postalAddress: myAddress
)

// レストラン予約を作成
let reservation = INRestaurantReservation(
    itemReference: reservationReference,
    reservationStatus: .confirmed,
    reservationHolderName: "Jane Appleseed",
    reservationDuration: duration,
    restaurantLocation: location
)

// intent と response を作成
let intent = INGetReservationDetailsIntent(reservationContainerReference: reservationReference)
let intentResponse = INGetReservationDetailsIntentResponse(code: .success, userActivity: nil)
intentResponse.reservations = [reservation]

// interaction を作成して寄付
let interaction = INInteraction(intent: intent, response: intentResponse)
interaction.donate()

キーポイント:

  • INSpeakableString には一意の vocabularyIdentifier が必要
  • INDateComponentsRange でイベントの時間範囲を定義
  • INRestaurantReservationINReservation のサブクラス、航空券・ホテルなどのサブクラスも存在
  • interaction.donate() でシステムにイベントを推薦し、カレンダー受信トレイに表示

Write-only 権限でイベント追加

12:41

import EventKit

// event store を作成
let store = EKEventStore()

// write-only 権限をリクエスト
guard try await store.requestWriteOnlyAccessToEvents() else { return }

// イベントを作成
let event = EKEvent(eventStore: store)
event.calendar = store.defaultCalendarForNewEvents
event.title = "WWDC23 Keynote"
event.startDate = myEventStartDate
event.endDate = myEventEndDate
event.timeZone = TimeZone(identifier: "America/Los_Angeles")
event.location = "1 Apple Park Way, Cupertino, CA, United States"
event.notes = "Kick off an exhilarating week of technology and community."

// イベントを保存
guard try store.save(event, span: .thisEvent) else { return }

キーポイント:

  • requestWriteOnlyAccessToEvents() は iOS 17 で追加
  • Info.plist に NSCalendarsWriteOnlyAccessUsageDescription が必要
  • calendar プロパティを設定し、defaultCalendarForNewEvents を使用
  • title、startDate、endDate は必須、その他はオプション

イベント取得(Full Access)

15:51

import EventKit

// event store を作成
let store = EKEventStore()

// full access をリクエスト
guard try await store.requestFullAccessToEvents() else { return }

// クエリ条件を作成: 今月
guard let interval = Calendar.current.dateInterval(of: .month, for: Date()) else { return }
let predicate = store.predicateForEvents(
    withStart: interval.start,
    end: interval.end,
    calendars: nil
)

// イベントを取得
let events = store.events(matching: predicate)

// 開始時刻でソート
let sortedEvents = events.sorted {
    $0.compareStartDate(with: $1) == .orderedAscending
}

キーポイント:

  • requestFullAccessToEvents() は iOS 17 で追加
  • Info.plist に NSCalendarsFullAccessUsageDescription が必要
  • predicateForEvents で時間範囲とオプションのカレンダーリストを指定
  • events(matching:) は順序なし配列を返すため、手動でソートが必要
  • 可能な限り短い時間範囲を使用してパフォーマンスを向上

仮想会議拡張

19:18

import EventKit

class VirtualConferenceProvider: EKVirtualConferenceProvider {

// 利用可能な会議室タイプを提供
    override func fetchAvailableRoomTypes() async throws -> [EKVirtualConferenceRoomTypeDescriptor] {
        let title = "My Room"
        let identifier = "my_room"
        let roomType = EKVirtualConferenceRoomTypeDescriptor(title: title, identifier: identifier)
        return [roomType]
    }

// 選択されたタイプに基づいて会議の詳細を提供
    override func fetchVirtualConference(
        identifier: EKVirtualConferenceRoomTypeIdentifier
    ) async throws -> EKVirtualConferenceDescriptor {
        let urlDescriptor = EKVirtualConferenceURLDescriptor(title: nil, url: myURL)
        let details = "Enter the meeting code 12345 to enter the meeting."
        return EKVirtualConferenceDescriptor(
            title: nil,
            urlDescriptors: [urlDescriptor],
            conferenceDetails: details
        )
    }
}

キーポイント:

  • Xcode で Virtual Conference Extension ターゲットを作成
  • fetchAvailableRoomTypes() はカレンダーの場所選択に表示される会議室タイプリストを返す
  • fetchVirtualConference() はユーザーが選択したタイプの会議リンクを返す
  • EKVirtualConferenceURLDescriptor の URL は Universal Links を推奨、アプリを直接開ける
  • conferenceDetails はイベント詳細の仮想会議エリアに表示

旧バージョンとの互換性

import EventKit

let store = EKEventStore()

if #available(iOS 17.0, macOS 14.0, *) {
// iOS 17+ では新しい方法を使用
    let granted = try await store.requestWriteOnlyAccessToEvents()
} else {
// 旧バージョンでは互換メソッドを使用
    let granted = try await store.requestAccess(to: .event)
}

キーポイント:

  • iOS 17 以前は requestAccess(to:)、以降は requestWriteOnlyAccessToEvents() または requestFullAccessToEvents()
  • iOS 17 以前のアプリには NSCalendarsUsageDescription が必要
  • EventKitUI を使用するアプリには NSContactsUsageDescription も必要
  • これらの説明文字列がないとクラッシュする

重要ポイント

1. チケット予約アプリのワンタップカレンダー追加

  • 何を作るか: 映画やコンサートのチケット購入後、ワンタップで会場情報をシステムカレンダーに追加
  • なぜ価値があるか: EKEventEditViewController は権限不要でユーザーの信頼度が高い
  • 始め方: EKEvent を作成し映画名、劇場住所、開演時間を入力、エディタを表示してユーザーに確認

2. レストラン予約アシスタント

  • 何を作るか: レストラン予約後、住所とナビゲーション付きでカレンダー受信トレイにイベントを自動推薦
  • なぜ価値があるか: Siri Event Suggestions は権限も UI も不要でシームレスな体験
  • 始め方: INRestaurantReservation を作成してシステムに donate、ユーザーはカレンダー受信トレイからワンタップで追加

3. チームスケジュール管理ツール

  • 何を作るか: ユーザーのシステムカレンダーを読み取り、仕事と個人のスケジュールを統合表示
  • なぜ価値があるか: full access で全カレンダーイベントを取得し、時間の調整を支援
  • 始め方: requestFullAccessToEvents() をリクエストし、predicateForEvents でクエリ、自分のスケジュールデータとマージ表示

4. ビデオ会議アプリのカレンダー統合

  • 何を作るか: カレンダーイベント作成時に場所選択から会議サービスを直接選択可能に
  • なぜ価値があるか: 仮想会議拡張でブランドがシステムカレンダーに表示され、ワンタップ参加のショートカットを提供
  • 始め方: Virtual Conference Extension ターゲットを作成し、2 つのプロトコルメソッドを実装、Universal Links でアプリに戻るよう設定

関連セッション

コメント

GitHub Issues · utterances