$npx -y skills add dpearson2699/swift-ios-skills --skill eventkitCreate, read, and manage calendar events and reminders using EventKit and EventKitUI. Use when adding events to the user's calendar, creating reminders, setting recurrence rules, requesting calendar or reminders access, presenting event editors, choosing calendars, handling alarm
| 1 | # EventKit |
| 2 | |
| 3 | Use EventKit for calendar and reminder authorization, CRUD, recurrence, alarms, |
| 4 | and system editors. |
| 5 | |
| 6 | ## Contents |
| 7 | |
| 8 | - [Availability](#availability) |
| 9 | - [Setup](#setup) |
| 10 | - [Authorization](#authorization) |
| 11 | - [Creating Events](#creating-events) |
| 12 | - [Fetching Events](#fetching-events) |
| 13 | - [Reminders](#reminders) |
| 14 | - [Recurrence Rules](#recurrence-rules) |
| 15 | - [Alarms](#alarms) |
| 16 | - [EventKitUI Controllers](#eventkitui-controllers) |
| 17 | - [Observing Changes](#observing-changes) |
| 18 | - [Common Mistakes](#common-mistakes) |
| 19 | - [Review Checklist](#review-checklist) |
| 20 | - [References](#references) |
| 21 | |
| 22 | ## Availability |
| 23 | |
| 24 | - **iOS 17+:** Use granular full/write-only request methods; legacy |
| 25 | `requestAccess(to:)` no longer prompts and throws. The system event editor can |
| 26 | create an event without app calendar access. For iOS 10–16, guard those APIs, |
| 27 | use the legacy request plus `NSCalendarsUsageDescription` / |
| 28 | `NSRemindersUsageDescription`; EventKitUI may also need |
| 29 | `NSContactsUsageDescription`. |
| 30 | - **iOS 26+:** The typed `EKEventStore.EventStoreChanged` / `.changed` message is |
| 31 | available behind a guard. Keep `EKEventStoreChanged` for earlier systems. |
| 32 | |
| 33 | ## Setup |
| 34 | |
| 35 | ### Info.plist Keys |
| 36 | |
| 37 | Add the usage description for the access path selected in |
| 38 | [Authorization](#authorization). Do not request broader access merely to simplify |
| 39 | the setup path. |
| 40 | |
| 41 | The authorization-free system editor path needs no calendar usage string. |
| 42 | Direct writes need write-only or full access; reads need full access. Reminders |
| 43 | have only full access. |
| 44 | |
| 45 | ### Event Store |
| 46 | |
| 47 | Create a single `EKEventStore` instance and reuse it. Do not mix objects from |
| 48 | different event stores. |
| 49 | |
| 50 | ```swift |
| 51 | import EventKit |
| 52 | |
| 53 | let eventStore = EKEventStore() |
| 54 | ``` |
| 55 | |
| 56 | ## Authorization |
| 57 | |
| 58 | Request the narrowest access that matches the feature. Apply the versioned |
| 59 | request path in [Availability](#availability). |
| 60 | |
| 61 | | Key | Access Level | |
| 62 | |---|---| |
| 63 | | `NSCalendarsFullAccessUsageDescription` | Read + write events | |
| 64 | | `NSCalendarsWriteOnlyAccessUsageDescription` | Direct write-only event creation | |
| 65 | | `NSRemindersFullAccessUsageDescription` | Read + write reminders | |
| 66 | |
| 67 | ### Full Access to Events |
| 68 | |
| 69 | Call `try await eventStore.requestFullAccessToEvents()` when the app needs to |
| 70 | read, edit, delete, or fetch calendar events. |
| 71 | |
| 72 | ### Write-Only Access to Events |
| 73 | |
| 74 | Use when your app only creates events (e.g., saving a booking) and does not |
| 75 | need to read existing events. |
| 76 | |
| 77 | Call `try await eventStore.requestWriteOnlyAccessToEvents()` before direct |
| 78 | EventKit writes that do not use `EKEventEditViewController`. |
| 79 | |
| 80 | Write-only access can create events but cannot fetch calendars or events, |
| 81 | including app-created events. Use full access for later query, verification, |
| 82 | modification, or sync. |
| 83 | |
| 84 | ### Full Access to Reminders |
| 85 | |
| 86 | Call `try await eventStore.requestFullAccessToReminders()` before reading, |
| 87 | creating, editing, or deleting reminders. |
| 88 | |
| 89 | ### Checking Authorization Status |
| 90 | |
| 91 | Use `EKEventStore.authorizationStatus(for: .event)` or `.reminder` before work. |
| 92 | Handle `.notDetermined`, `.fullAccess`, `.writeOnly`, `.restricted`, `.denied`, |
| 93 | and `@unknown default`; only `.fullAccess` supports event/reminder reads. |
| 94 | |
| 95 | ## Creating Events |
| 96 | |
| 97 | ```swift |
| 98 | func createEvent( |
| 99 | title: String, |
| 100 | startDate: Date, |
| 101 | endDate: Date, |
| 102 | calendar: EKCalendar? = nil |
| 103 | ) throws { |
| 104 | let event = EKEvent(eventStore: eventStore) |
| 105 | event.title = title |
| 106 | event.startDate = startDate |
| 107 | event.endDate = endDate |
| 108 | event.calendar = calendar ?? eventStore.defaultCalendarForNewEvents |
| 109 | |
| 110 | try eventStore.save(event, span: .thisEvent) |
| 111 | } |
| 112 | ``` |
| 113 | |
| 114 | ### Setting a Specific Calendar |
| 115 | |
| 116 | ```swift |
| 117 | // List writable calendars |
| 118 | let calendars = eventStore.calendars(for: .event) |
| 119 | .filter { $0.allowsContentModifications } |
| 120 | |
| 121 | // Use the first writable calendar, or the default |
| 122 | let targetCalendar = calendars.first ?? eventStore.defaultCalendarForNewEvents |
| 123 | event.calendar = targetCalendar |
| 124 | ``` |
| 125 | |
| 126 | ### Adding Structured Location |
| 127 | |
| 128 | ```swift |
| 129 | import CoreLocation |
| 130 | |
| 131 | let location = EKStructuredLocation(title: "Apple Park") |
| 132 | location.geoLocation = CLLocation(latitude: 37.3349, longitude: -122.0090) |
| 133 | event.structuredLocation = location |
| 134 | ``` |
| 135 | |
| 136 | ## Fetching Events |
| 137 | |
| 138 | After the full-access gate in [Authorization](#authorization), use a date-range |
| 139 | predicate to query events. The `events(matching:)` method returns occurrences of |
| 140 | recurring events expanded within the range. Event predicates are capped to a |
| 141 | four-year span, and `events(matching:)` / |
| 142 | `enumerateEvents(matching:using:)` are synchronous and return only committed |
| 143 | events. |
| 144 | |
| 145 | ```swift |
| 146 | func fetchEvents(from start: Date, |