How is an EventKit event modelled: recurrence rules, alarms, calendars, and why does saving fail?
Predict first
What happens to an unsaved event and a monthly rule?
import Foundation
import EventKit
print("status raw values: fullAccess", EKAuthorizationStatus.fullAccess.rawValue, "writeOnly", EKAuthorizationStatus.writeOnly.rawValue)
let store = EKEventStore()
let ev = EKEvent(eventStore: store)
ev.title = "Standup"
ev.startDate = Date(timeIntervalSince1970: 1_800_000_000); ev.endDate = ev.startDate.addingTimeInterval(900)
print("calendar before saving:", ev.calendar == nil ? "nil" : "set", "identifier:", ev.eventIdentifier ?? "nil")
do { try store.save(ev, span: .thisEvent) } catch { print("save fails:", (error as NSError).domain, (error as NSError).code, "(1 = no calendar)") }
let lastFriday = EKRecurrenceRule(recurrenceWith: .monthly, interval: 1, daysOfTheWeek: [EKRecurrenceDayOfWeek(.friday, weekNumber: -1)], daysOfTheMonth: nil, monthsOfTheYear: nil, weeksOfTheYear: nil, daysOfTheYear: nil, setPositions: nil, end: EKRecurrenceEnd(occurrenceCount: 6))
print("rule:", lastFriday.description.components(separatedBy: "> ").last!)
ev.addRecurrenceRule(lastFriday); ev.addAlarm(EKAlarm(relativeOffset: -600))
print("has rules:", ev.hasRecurrenceRules, "alarm offset:", ev.alarms![0].relativeOffset, "all day:", ev.isAllDay)
- Calendar is nil, save fails with code 1, and the rule prints as an RRULE
- Save succeeds
- The rule has no description
Mental model. An event is a sticky note with no page yet; a calendar is the page it must be stuck to before it counts.
An EKEvent is created from an EKEventStore and is not yet in any calendar: in the run calendar and eventIdentifier are nil, and save fails with EKErrorDomain code 1 (no calendar) until you assign one (store.defaultCalendarForNewEvents or a calendar you chose). Recurrence is an EKRecurrenceRule that renders to an iCalendar rule: “last Friday of the month, six times” prints as FREQ=MONTHLY;INTERVAL=1;COUNT=6;BYDAY=-1FR, so you can reason about it with RFC 5545. Alarms are relative offsets (−600 s is ten minutes before). The authorization statuses split since iOS 17: fullAccess (3) reads and writes, writeOnly (4) can only add, and writing needs a calendar you are allowed to use.
Practical rules: use the out-of-process EKEventEditViewController when you only need the user to add an event (no permission), save recurring events with the right EKSpan (this event or future events), store the eventIdentifier but expect it to change for some sources, and observe EKEventStoreChanged to refresh. Real saves and the picker were not run here.
An event needs a calendar and recurrence maps to RRULE text.
Common traps
- Saving an event with no calendar. It fails with a no-calendar error.
- Using
.thisEventon a recurring edit that should change the future. Pick the span deliberately.
They might follow up with
- Do I need permission to add an event? Not with the edit view controller; write-only access otherwise.
Related questions
- 17.3.1 How do the Contacts, EventKit and Photos frameworks gate access, and how should an app handle limited or…
← What can you do with the Contacts framework without permission, and… How do HealthKit units and aggregation styles work, and why does… →