ハイライト
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でイベントの時間範囲を定義INRestaurantReservationはINReservationのサブクラス、航空券・ホテルなどのサブクラスも存在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 でアプリに戻るよう設定
関連セッション
- What’s new in privacy — カレンダー権限の改善とプライバシー設計
- Broaden your reach with Siri Event Suggestions — Siri Event Suggestions API の詳細
- Build better document-based apps — ドキュメントベースアプリの開発
コメント
GitHub Issues · utterances