Add scan flow MVP and local Axiom skill workspace
This snapshot establishes the camera-to-result recognition flow and related tests while checking in the project skill/docs assets required for the configured local tooling.
This commit is contained in:
611
.claude/skills/axiom-eventkit-ref/SKILL.md
Normal file
611
.claude/skills/axiom-eventkit-ref/SKILL.md
Normal file
@@ -0,0 +1,611 @@
|
||||
---
|
||||
name: axiom-eventkit-ref
|
||||
description: Use when needing EventKit API details — EKEventStore, EKEvent, EKReminder, EventKitUI view controllers, EKCalendarChooser, authorization methods, predicate-based fetching, recurrence rules, Siri Event Suggestions donation, EKVirtualConferenceProvider, location-based reminders, and EKErrorDomain codes
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# EventKit API Reference
|
||||
|
||||
## Overview
|
||||
|
||||
EventKit provides programmatic access to the Calendar and Reminders databases. EventKitUI provides system view controllers for calendar UI. This reference covers the complete API surface for both frameworks.
|
||||
|
||||
For access tier decision tree and best practices, see the **eventkit** discipline skill.
|
||||
|
||||
**Platform**: iOS 4.0+, iPadOS 4.0+, macOS 10.8+, Mac Catalyst 13.1+, watchOS 2.0+, visionOS 1.0+
|
||||
|
||||
---
|
||||
|
||||
# Part 1: EKEventStore
|
||||
|
||||
The central hub for all calendar and reminder operations. Create one per app and reuse it.
|
||||
|
||||
## Initialization
|
||||
|
||||
```swift
|
||||
let store = EKEventStore() // Standard
|
||||
let store = EKEventStore(sources: [source]) // Scoped to specific sources
|
||||
```
|
||||
|
||||
## Authorization (iOS 17+)
|
||||
|
||||
```swift
|
||||
// Events
|
||||
try await store.requestWriteOnlyAccessToEvents() // Returns Bool
|
||||
try await store.requestFullAccessToEvents() // Returns Bool
|
||||
|
||||
// Reminders (full access only)
|
||||
try await store.requestFullAccessToReminders() // Returns Bool
|
||||
|
||||
// Check status
|
||||
let status = EKEventStore.authorizationStatus(for: .event) // Static method
|
||||
// Returns: .notDetermined, .restricted, .denied, .fullAccess, .writeOnly
|
||||
// Deprecated: .authorized (maps to .fullAccess conceptually)
|
||||
```
|
||||
|
||||
## Info.plist Keys
|
||||
|
||||
| Key | When Required |
|
||||
|-----|---------------|
|
||||
| `NSCalendarsWriteOnlyAccessUsageDescription` | Write-only access, iOS 17+ |
|
||||
| `NSCalendarsFullAccessUsageDescription` | Full event access, iOS 17+ |
|
||||
| `NSRemindersFullAccessUsageDescription` | Reminder access, iOS 17+ |
|
||||
| `NSCalendarsUsageDescription` | Calendar access, iOS 10-16 (keep for backward compat) |
|
||||
| `NSRemindersUsageDescription` | Reminder access, iOS 10-16 (keep for backward compat) |
|
||||
| `NSContactsUsageDescription` | Required if using EventKitUI on iOS <17 |
|
||||
|
||||
**Missing key on iOS 17+**: Silent denial (no prompt, no error, no crash).
|
||||
**Missing key on iOS 10-16**: Crash.
|
||||
|
||||
## Calendar & Source Access
|
||||
|
||||
```swift
|
||||
store.calendars(for: .event) // [EKCalendar] — all event calendars
|
||||
store.calendars(for: .reminder) // [EKCalendar] — all reminder calendars
|
||||
store.calendar(withIdentifier: id) // EKCalendar?
|
||||
store.defaultCalendarForNewEvents // EKCalendar? — user's default
|
||||
store.defaultCalendarForNewReminders() // EKCalendar?
|
||||
store.sources // [EKSource] — all accounts
|
||||
store.delegateSources // [EKSource] — delegate accounts
|
||||
```
|
||||
|
||||
## Calendar Management
|
||||
|
||||
```swift
|
||||
try store.saveCalendar(calendar, commit: true)
|
||||
try store.removeCalendar(calendar, commit: true)
|
||||
```
|
||||
|
||||
## Event Operations
|
||||
|
||||
```swift
|
||||
// Fetch by identifier
|
||||
store.event(withIdentifier: id) // EKEvent? — first occurrence for recurring
|
||||
store.calendarItem(withIdentifier: id) // EKCalendarItem? — event or reminder
|
||||
store.calendarItems(withExternalIdentifier: extId) // [EKCalendarItem]
|
||||
|
||||
// Save and remove
|
||||
try store.save(event, span: .thisEvent, commit: true)
|
||||
try store.remove(event, span: .thisEvent, commit: true)
|
||||
// span: .thisEvent | .futureEvents (controls recurring event behavior)
|
||||
```
|
||||
|
||||
## Event Fetching (Synchronous — run on background thread)
|
||||
|
||||
```swift
|
||||
let predicate = store.predicateForEvents(
|
||||
withStart: startDate, end: endDate, calendars: nil // nil = all calendars
|
||||
)
|
||||
let events = store.events(matching: predicate)
|
||||
// Results are NOT sorted — sort manually:
|
||||
let sorted = events.sorted { $0.compareStartDate(with: $1) == .orderedAscending }
|
||||
```
|
||||
|
||||
**Only Apple-provided predicates work.** Custom `NSPredicate` instances are rejected.
|
||||
|
||||
## Reminder Fetching (Asynchronous)
|
||||
|
||||
```swift
|
||||
// Predicates
|
||||
store.predicateForReminders(in: calendars) // nil = all
|
||||
store.predicateForIncompleteReminders(
|
||||
withDueDateStarting: start, ending: end, calendars: nil
|
||||
)
|
||||
store.predicateForCompletedReminders(
|
||||
withCompletionDateStarting: start, ending: end, calendars: nil
|
||||
)
|
||||
|
||||
// Fetch (async callback)
|
||||
let fetchId = store.fetchReminders(matching: predicate) { reminders in
|
||||
// reminders: [EKReminder]?
|
||||
}
|
||||
store.cancelFetchRequest(fetchId) // Cancel if needed
|
||||
```
|
||||
|
||||
## Batch Operations
|
||||
|
||||
```swift
|
||||
try store.save(event1, span: .thisEvent, commit: false)
|
||||
try store.save(event2, span: .thisEvent, commit: false)
|
||||
try store.commit() // Atomic commit
|
||||
store.reset() // Rollback on failure
|
||||
```
|
||||
|
||||
## Change Notifications
|
||||
|
||||
```swift
|
||||
NotificationCenter.default.addObserver(
|
||||
self, selector: #selector(storeChanged),
|
||||
name: .EKEventStoreChanged, object: store
|
||||
)
|
||||
// Posted when external processes modify the calendar database
|
||||
// Call event.refresh() on cached objects — returns false if deleted
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Part 2: EKEvent
|
||||
|
||||
Represents a calendar event. Inherits from `EKCalendarItem`.
|
||||
|
||||
## Creation
|
||||
|
||||
```swift
|
||||
let event = EKEvent(eventStore: store)
|
||||
```
|
||||
|
||||
## Key Properties
|
||||
|
||||
| Property | Type | Notes |
|
||||
|----------|------|-------|
|
||||
| `title` | `String` | Required for save |
|
||||
| `startDate` | `Date` | Required for save |
|
||||
| `endDate` | `Date` | Required for save |
|
||||
| `calendar` | `EKCalendar` | Required for direct save (not EventKitUI) |
|
||||
| `isAllDay` | `Bool` | |
|
||||
| `timeZone` | `TimeZone?` | Defaults to system time zone |
|
||||
| `location` | `String?` | Full address enables Maps features |
|
||||
| `structuredLocation` | `EKStructuredLocation?` | Geo-precise location |
|
||||
| `notes` | `String?` | |
|
||||
| `url` | `URL?` | |
|
||||
| `eventIdentifier` | `String` | Stable across fetches |
|
||||
| `status` | `EKEventStatus` | `.none`, `.confirmed`, `.tentative`, `.canceled` |
|
||||
| `availability` | `EKEventAvailability` | `.notSupported` (default), `.busy`, `.free`, `.tentative`, `.unavailable` |
|
||||
| `occurrenceDate` | `Date` | For recurring event instances |
|
||||
| `isDetached` | `Bool` | True if modified from recurring series |
|
||||
| `organizer` | `EKParticipant?` | Read-only |
|
||||
| `birthdayContactIdentifier` | `String?` | For birthday calendar events |
|
||||
|
||||
## Inherited from EKCalendarItem
|
||||
|
||||
| Property | Type | Notes |
|
||||
|----------|------|-------|
|
||||
| `calendarItemIdentifier` | `String` | Unique identifier |
|
||||
| `calendarItemExternalIdentifier` | `String` | External (sync) identifier |
|
||||
| `creationDate` | `Date?` | |
|
||||
| `lastModifiedDate` | `Date?` | |
|
||||
| `alarms` | `[EKAlarm]?` | |
|
||||
| `recurrenceRules` | `[EKRecurrenceRule]?` | |
|
||||
| `hasAlarms` | `Bool` | |
|
||||
| `hasRecurrenceRules` | `Bool` | |
|
||||
| `attendees` | `[EKParticipant]?` | Read-only |
|
||||
|
||||
## Methods
|
||||
|
||||
```swift
|
||||
event.compareStartDate(with: otherEvent) // ComparisonResult
|
||||
event.refresh() // Bool — false if deleted
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Part 3: EKReminder
|
||||
|
||||
Represents a reminder. Inherits from `EKCalendarItem`.
|
||||
|
||||
## Creation
|
||||
|
||||
```swift
|
||||
let reminder = EKReminder(eventStore: store)
|
||||
reminder.title = "Review PR"
|
||||
reminder.calendar = store.defaultCalendarForNewReminders() // Required
|
||||
```
|
||||
|
||||
## Key Properties
|
||||
|
||||
| Property | Type | Notes |
|
||||
|----------|------|-------|
|
||||
| `startDateComponents` | `DateComponents?` | Task start |
|
||||
| `dueDateComponents` | `DateComponents?` | Due date — use `DateComponents`, NOT `Date` |
|
||||
| `isCompleted` | `Bool` | Setting true auto-populates `completionDate` |
|
||||
| `completionDate` | `Date?` | Auto-set when `isCompleted = true` |
|
||||
| `priority` | `Int` | Use `EKReminderPriority` raw values |
|
||||
|
||||
## EKReminderPriority
|
||||
|
||||
| Case | Raw Value |
|
||||
|------|-----------|
|
||||
| `.none` | 0 |
|
||||
| `.high` | 1 |
|
||||
| `.medium` | 5 |
|
||||
| `.low` | 9 |
|
||||
|
||||
## Save/Remove
|
||||
|
||||
```swift
|
||||
try store.save(reminder, commit: true)
|
||||
try store.remove(reminder, commit: true)
|
||||
// No span parameter — reminders don't have recurring instances like events
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Part 4: EKAlarm
|
||||
|
||||
Notification alarm for events or reminders.
|
||||
|
||||
```swift
|
||||
// Time-based
|
||||
let absoluteAlarm = EKAlarm(absoluteDate: date) // Specific date/time
|
||||
let relativeAlarm = EKAlarm(relativeOffset: -3600) // 1 hour before (seconds)
|
||||
|
||||
// Location-based (EKAlarm.proximity available since iOS 6.0+)
|
||||
let location = EKStructuredLocation(title: "Office")
|
||||
location.geoLocation = CLLocation(latitude: 37.33, longitude: -122.03)
|
||||
location.radius = 500 // meters
|
||||
|
||||
let locationAlarm = EKAlarm()
|
||||
locationAlarm.structuredLocation = location
|
||||
locationAlarm.proximity = .enter // .enter or .leave
|
||||
|
||||
reminder.addAlarm(locationAlarm)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Part 5: EKRecurrenceRule
|
||||
|
||||
```swift
|
||||
let rule = EKRecurrenceRule(
|
||||
recurrenceWith: .weekly, // .daily, .weekly, .monthly, .yearly
|
||||
interval: 1, // Every 1 week
|
||||
daysOfTheWeek: [EKRecurrenceDayOfWeek(.monday), EKRecurrenceDayOfWeek(.wednesday)],
|
||||
daysOfTheMonth: nil,
|
||||
monthsOfTheYear: nil,
|
||||
weeksOfTheYear: nil,
|
||||
daysOfTheYear: nil,
|
||||
setPositions: nil,
|
||||
end: EKRecurrenceEnd(occurrenceCount: 10) // or EKRecurrenceEnd(end: Date)
|
||||
)
|
||||
event.addRecurrenceRule(rule)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Part 6: EKCalendar and EKSource
|
||||
|
||||
## EKCalendar Properties
|
||||
|
||||
| Property | Type | Notes |
|
||||
|----------|------|-------|
|
||||
| `title` | `String` | |
|
||||
| `color` | `UIColor` / `cgColor: CGColor` | |
|
||||
| `type` | `EKCalendarType` | `.local`, `.calDAV`, `.exchange`, `.subscription`, `.birthday` |
|
||||
| `allowsContentModifications` | `Bool` | Can write to this calendar? |
|
||||
| `isImmutable` | `Bool` | System calendar (birthday, holidays) |
|
||||
| `source` | `EKSource` | Parent account |
|
||||
|
||||
## EKSource Properties
|
||||
|
||||
| Property | Type |
|
||||
|----------|------|
|
||||
| `title` | `String` |
|
||||
| `sourceType` | `EKSourceType` — `.local`, `.exchange`, `.calDAV`, `.mobileMe`, `.subscribed`, `.birthdays` |
|
||||
| `sourceIdentifier` | `String` |
|
||||
|
||||
---
|
||||
|
||||
# Part 7: EventKitUI View Controllers
|
||||
|
||||
## EKEventEditViewController
|
||||
|
||||
Create/edit events. **No permission required on iOS 17+** (renders out-of-process).
|
||||
|
||||
**Inherits from**: `UINavigationController` (NOT `UIViewController`)
|
||||
|
||||
```swift
|
||||
let editVC = EKEventEditViewController()
|
||||
editVC.event = event // nil = new event
|
||||
editVC.eventStore = store // Required
|
||||
editVC.editViewDelegate = self
|
||||
present(editVC, animated: true)
|
||||
```
|
||||
|
||||
### EKEventEditViewDelegate
|
||||
|
||||
```swift
|
||||
func eventEditViewController(
|
||||
_ controller: EKEventEditViewController,
|
||||
didCompleteWith action: EKEventEditViewAction
|
||||
) {
|
||||
// action: .canceled, .saved, .deleted
|
||||
dismiss(animated: true)
|
||||
}
|
||||
|
||||
func eventEditViewControllerDefaultCalendar(
|
||||
forNewEvents controller: EKEventEditViewController
|
||||
) -> EKCalendar {
|
||||
return store.defaultCalendarForNewEvents!
|
||||
}
|
||||
```
|
||||
|
||||
## EKEventViewController
|
||||
|
||||
Display event details. **Requires full access**.
|
||||
|
||||
**Inherits from**: `UIViewController` (can push onto nav stack)
|
||||
|
||||
```swift
|
||||
let viewVC = EKEventViewController()
|
||||
viewVC.event = event // Required
|
||||
viewVC.allowsEditing = true
|
||||
viewVC.allowsCalendarPreview = true
|
||||
viewVC.delegate = self
|
||||
navigationController?.pushViewController(viewVC, animated: true)
|
||||
```
|
||||
|
||||
### EKEventViewDelegate
|
||||
|
||||
```swift
|
||||
func eventViewController(
|
||||
_ controller: EKEventViewController,
|
||||
didCompleteWith action: EKEventViewAction
|
||||
) {
|
||||
// action: .done, .responded, .deleted
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: `EKEventViewController` automatically handles `EKEventStoreChanged` notifications — no manual refresh needed.
|
||||
|
||||
## EKCalendarChooser
|
||||
|
||||
Calendar selection UI. **Requires write-only or full access**.
|
||||
|
||||
```swift
|
||||
let chooser = EKCalendarChooser(
|
||||
selectionStyle: .single, // .single or .multiple
|
||||
displayStyle: .writableCalendarsOnly, // .allCalendars or .writableCalendarsOnly
|
||||
entityType: .event, // .event or .reminder
|
||||
eventStore: store
|
||||
)
|
||||
chooser.selectedCalendars = [store.defaultCalendarForNewEvents!]
|
||||
chooser.showsDoneButton = true
|
||||
chooser.delegate = self
|
||||
present(UINavigationController(rootViewController: chooser), animated: true)
|
||||
```
|
||||
|
||||
**Gotcha**: Under write-only access, `displayStyle` is ignored — always shows writable only.
|
||||
|
||||
---
|
||||
|
||||
# Part 8: Virtual Conference Extension
|
||||
|
||||
For apps supporting voice/video calls — integrates directly into Calendar's location picker.
|
||||
|
||||
## Extension Setup
|
||||
|
||||
1. Add Virtual Conference Extension target in Xcode
|
||||
2. Extension point: `com.apple.calendar.virtualconference`
|
||||
3. Template generates `EKVirtualConferenceProvider` subclass
|
||||
|
||||
## EKVirtualConferenceProvider
|
||||
|
||||
**Platform**: iOS 15.0+, macOS 12.0+, watchOS 8.0+, visionOS 1.0+
|
||||
|
||||
```swift
|
||||
class MyConferenceProvider: EKVirtualConferenceProvider {
|
||||
override func fetchAvailableRoomTypes() async throws
|
||||
-> [EKVirtualConferenceRoomTypeDescriptor] {
|
||||
return [
|
||||
EKVirtualConferenceRoomTypeDescriptor(
|
||||
title: "Personal Room",
|
||||
identifier: "personal_room"
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
override func fetchVirtualConference(
|
||||
identifier: EKVirtualConferenceRoomTypeIdentifier
|
||||
) async throws -> EKVirtualConferenceDescriptor {
|
||||
let url = EKVirtualConferenceURLDescriptor(
|
||||
title: nil, // Optional — useful when multiple join URLs
|
||||
url: URL(string: "https://myapp.com/join/\(roomId)")!
|
||||
)
|
||||
return EKVirtualConferenceDescriptor(
|
||||
title: nil, // Optional — distinguishes multiple room types
|
||||
urlDescriptors: [url],
|
||||
conferenceDetails: "Enter code 12345 to join"
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Use Universal Links** for join URLs so your app opens directly.
|
||||
|
||||
**Syncing**: Events with virtual conference info sync to devices where your app may not be installed.
|
||||
|
||||
---
|
||||
|
||||
# Part 9: Siri Event Suggestions
|
||||
|
||||
Add reservation-style events to Calendar without requesting any permission. Events appear in the Calendar inbox like invitations.
|
||||
|
||||
**Supported types**: restaurant, hotel, flight, train, bus, boat, rental car, ticketed events
|
||||
|
||||
```swift
|
||||
// 1. Create reservation reference
|
||||
let reference = INSpeakableString(
|
||||
vocabularyIdentifier: "booking-\(reservationId)",
|
||||
spokenPhrase: "Dinner at Caffè Macs",
|
||||
pronunciationHint: nil
|
||||
)
|
||||
|
||||
// 2. Create reservation
|
||||
let duration = INDateComponentsRange(start: startComponents, end: endComponents)
|
||||
let location = MKPlacemark(coordinate: clLocation.coordinate, postalAddress: address)
|
||||
|
||||
let reservation = INRestaurantReservation(
|
||||
itemReference: reference,
|
||||
reservationStatus: .confirmed,
|
||||
reservationHolderName: "Jane Appleseed",
|
||||
reservationDuration: duration,
|
||||
restaurantLocation: location
|
||||
)
|
||||
|
||||
// 3. Create intent + response
|
||||
let intent = INGetReservationDetailsIntent(
|
||||
reservationContainerReference: reference
|
||||
)
|
||||
let response = INGetReservationDetailsIntentResponse(code: .success, userActivity: nil)
|
||||
response.reservations = [reservation]
|
||||
|
||||
// 4. Donate interaction
|
||||
let interaction = INInteraction(intent: intent, response: response)
|
||||
interaction.donate()
|
||||
```
|
||||
|
||||
### Reservation Types
|
||||
|
||||
| Type | Class |
|
||||
|------|-------|
|
||||
| Restaurant | `INRestaurantReservation` |
|
||||
| Hotel | `INLodgingReservation` |
|
||||
| Flight | `INFlightReservation` |
|
||||
| Train | `INTrainReservation` |
|
||||
| Bus | `INBusReservation` (iOS 14+) |
|
||||
| Boat | `INBoatReservation` (iOS 14+) |
|
||||
| Rental Car | `INRentalCarReservation` |
|
||||
| Ticketed Event | `INTicketedEventReservation` |
|
||||
|
||||
### Update/Cancel
|
||||
|
||||
Use the same `reservationId` across donations:
|
||||
- **Update**: Donate with updated details, same `reservationId`
|
||||
- **Cancel**: Set `reservationStatus = .canceled` and re-donate
|
||||
|
||||
### Web Markup (iOS 14+)
|
||||
|
||||
Embed schema.org JSON-LD or Microdata in HTML for Safari and Mail:
|
||||
```html
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "FoodEstablishmentReservation",
|
||||
"reservationId": "abc123",
|
||||
"reservationStatus": "https://schema.org/ReservationConfirmed",
|
||||
"startTime": "2024-06-15T19:30:00-07:00",
|
||||
"underName": { "@type": "Person", "name": "Jane" },
|
||||
"reservationFor": {
|
||||
"@type": "FoodEstablishment",
|
||||
"name": "Caffè Macs",
|
||||
"address": "1 Apple Park Way, Cupertino, CA"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
**Requires**: Domain registration with Apple, HTTPS, valid DKIM for emails.
|
||||
|
||||
### Show in App / Show in Safari
|
||||
|
||||
- When app is installed: Calendar shows "Show in App" button — launches app with `INGetReservationDetailsIntent`
|
||||
- When app is not installed: If `url` property is set on `INReservation`, Calendar shows "Show in Safari"
|
||||
|
||||
---
|
||||
|
||||
# Part 10: Location-Based Reminders
|
||||
|
||||
**Platform**: iOS 6.0+ (EKAlarm.proximity, EKStructuredLocation)
|
||||
|
||||
**Required permissions**: Location When In Use + Full Reminders Access
|
||||
|
||||
```swift
|
||||
// Create location-triggered reminder
|
||||
let reminder = EKReminder(eventStore: store)
|
||||
reminder.title = "Pick up dry cleaning"
|
||||
reminder.calendar = store.defaultCalendarForNewReminders()
|
||||
|
||||
let location = EKStructuredLocation(title: "Dry Cleaners")
|
||||
location.geoLocation = CLLocation(latitude: 37.33, longitude: -122.03)
|
||||
location.radius = 200 // meters
|
||||
|
||||
let alarm = EKAlarm()
|
||||
alarm.structuredLocation = location
|
||||
alarm.proximity = .enter // .enter or .leave
|
||||
reminder.addAlarm(alarm)
|
||||
|
||||
try store.save(reminder, commit: true)
|
||||
```
|
||||
|
||||
### Fetching Location Reminders
|
||||
|
||||
```swift
|
||||
let predicate = store.predicateForReminders(in: nil)
|
||||
let allReminders = try await fetchReminders(matching: predicate)
|
||||
let locationReminders = allReminders.filter { reminder in
|
||||
reminder.alarms?.contains { alarm in
|
||||
alarm.structuredLocation != nil && alarm.proximity != .none
|
||||
} ?? false
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Part 11: Error Reference
|
||||
|
||||
## EKErrorDomain Codes
|
||||
|
||||
| Code | Name | Meaning |
|
||||
|------|------|---------|
|
||||
| 0 | `eventNotMutable` | Event is read-only |
|
||||
| 1 | `noCalendar` | Calendar property not set |
|
||||
| 2 | `noStartDate` | Missing start date |
|
||||
| 3 | `noEndDate` | Missing end date |
|
||||
| 4 | `datesInverted` | End date before start date |
|
||||
| 12 | `calendarReadOnly` | Calendar doesn't allow modifications |
|
||||
| 13 | `calendarIsImmutable` | System calendar (birthday, etc.) |
|
||||
| 15 | `sourceDoesNotAllowCalendarAddDelete` | Can't create/delete calendars on this source |
|
||||
| 18 | `recurringReminderRequiresDueDate` | Recurring reminders need due date |
|
||||
| 19 | `structuredLocationsNotSupported` | Location alarms not supported |
|
||||
| 21 | `alarmProximityNotSupported` | Proximity alarms not supported |
|
||||
| 22 | `eventStoreNotAuthorized` | No permission |
|
||||
| 24 | `objectBelongsToDifferentStore` | Cross-store object usage |
|
||||
| 25 | `invitesCannotBeMoved` | Can't move events with attendees |
|
||||
| 26 | `invalidSpan` | Invalid span value |
|
||||
|
||||
---
|
||||
|
||||
# Part 12: Platform Availability Matrix
|
||||
|
||||
| API | iOS | macOS | watchOS | visionOS |
|
||||
|-----|-----|-------|---------|----------|
|
||||
| EKEventStore | 4.0+ | 10.8+ | 2.0+ | 1.0+ |
|
||||
| Write-only access | 17.0+ | 14.0+ | 10.0+ | 1.0+ |
|
||||
| Full access (new API) | 17.0+ | 14.0+ | 10.0+ | 1.0+ |
|
||||
| EKEventEditViewController | 4.0+ | (Catalyst 13.1+) | — | 1.0+ |
|
||||
| EKEventViewController | 4.0+ | (Catalyst 13.1+) | — | 1.0+ |
|
||||
| EKCalendarChooser | 4.0+ | (Catalyst 13.0+) | — | 1.0+ |
|
||||
| EKVirtualConferenceProvider | 15.0+ | 12.0+ | 8.0+ | 1.0+ |
|
||||
| Location-based reminders | 6.0+ | 10.8+ | — | — |
|
||||
| Siri Event Suggestions | 12.0+ | 11.0+ (Catalyst) | — | — |
|
||||
| Schema.org markup | 14.0+ | 11.0+ (Safari/Mail) | — | — |
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
**WWDC**: 2023-10052, 2020-10197
|
||||
|
||||
**Docs**: /eventkit, /eventkitui, /eventkit/ekeventstore, /eventkit/ekevent, /eventkit/ekreminder, /eventkit/ekvirtualconferenceprovider, /technotes/tn3152, /technotes/tn3153
|
||||
|
||||
**Skills**: eventkit, contacts-ref, extensions-widgets-ref
|
||||
Reference in New Issue
Block a user