Skip to main content

device_calendar_plus_ios 0.11.0 copy "device_calendar_plus_ios: ^0.11.0" to clipboard
device_calendar_plus_ios: ^0.11.0 copied to clipboard

PlatformiOS

iOS implementation of the device_calendar_plus plugin.

0.11.0 - 2026-10-19 #

Added #

  • Event reads report the organizer from EKEvent.organizer (#236).

Fixed #

  • An iCloud source titled with the account's email (user@me.com, @icloud.com, @mac.com) is now recognised: listSources reports it can create calendars, and createCalendar without a source uses it. Thanks @eikob (#233). An iCloud+ custom-domain title still isn't recognised, since EventKit gives no other way to tell iCloud from Google.
  • updateRecurring with thisAndFollowing that makes an all-day series timed lost the new series' last occurrence across a DST change, and west of UTC left the split occurrence on the original series too. It now splits first and makes the new series timed in a second save (#232).
  • updateRecurring with thisAndFollowing that clears the rule from a series' first occurrence now edits the series in place and keeps its ID.

0.10.0 - 2026-10-18 #

Changed #

  • Breaking: implements the platform interface's new android: / ios: options. CreateCalendarOptionsIos moves to the platform interface as CalendarOptionsIos. Android-only options are ignored, and an updateEvent or updateRecurring call whose only change is Android-only writes nothing (#70).

0.9.2 - 2026-10-14 #

Fixed #

  • updateEvent with isAllDay: false and new dates on one occurrence of an all-day series saves in two steps too — the other changes first, which detaches the occurrence, then the timed flip with the dates. EventKit's iCloud store persisted the hours as UTC wall clock when the detach and the flip happened in one save: a 09:00–10:00 occurrence in Los Angeles read back as 02:00–03:00, though the saved EKEvent reported 16:00 UTC and its zone right up to the commit. The reverse of #210's flip; the local store, and so the simulator, got the one-step save right (#212). Either flip now checks that its first save detached the occurrence and fails with operationFailed, the occurrence unchanged, when it did not, rather than flipping an attached occurrence in one save.
  • updateRecurring with allEvents and a start on another day of a rule that pins days (Monday of a Mon/Wed rule to its Wednesday) moves the series. EventKit read the .futureEvents save, with the loaded rule left untouched, as moving one occurrence: it left the master where it was, detached the first occurrence onto the new day with no rule, and the plugin returned that occurrence's /RID= ID. The kept rule is now set on the event again for such a move, which makes the save a series edit (#206).
  • updateRecurring with thisAndFollowing and a start on another day the kept rule generates splits the series at the occurrence, with the rule's remaining count carried over, instead of refusing the move while the rule pins days. The same re-set of the kept rule makes EventKit split the series rather than detach the occurrence (#194).
  • updateEvent with isAllDay: true on one occurrence of a timed series saves in two steps — the other changes and the dates first, which detaches the occurrence, then the all-day flip. EventKit's iCloud store persisted the detached occurrence's end a day late when the detach and the flip happened in one save, whatever end was written: a 9–10am occurrence read back as two days and a 23:30–00:30 one as three. The local store, and so the simulator, got the one-step save right. Without startDate or endDate, on a one-off as well as an occurrence, the all-day span is now also written explicitly — local midnight of the start day through the last second of the last day the timed event touched, the span Android writes (#203) in #195's end convention — rather than left to EventKit.

0.9.1 - 2026-10-01 #

Fixed #

  • updateRecurring refuses a start move with no new rule unless the existing rule generates the series' new start. It compared only the weekday, day of month and month the rule pins, so an ordinal BYDAY or a BYSETPOS series (the 4th Thursday of November, say) could move to another occurrence of its weekday (the 3rd Thursday), leaving the start on a day the rule never generates (#189). A move onto another day the rule generates (Monday to Wednesday of a Mon/Wed/Fri rule) is now allowed through allEvents; with thisAndFollowing a move to another day still throws while the rule pins days, until iOS can split such a series (#194).
  • updateRecurring on an all-day series adds duration (or keeps the existing span, when only start moves) in calendar days rather than 86,400-second days, so a multi-day all-day series no longer loses or gains its last day when its span crosses a DST change (#195).
  • updateEvent and updateRecurring write an all-day event's end as the last second of its last day, the way EventKit stores it. An exclusive midnight end written onto an existing all-day event read back as one more day, so a two-day duration or end gave three (#195).

0.9.0 - 2026-09-30 #

Changed #

  • Breaking: updateEvent and deleteEvent without a timestamp refuse a recurring series with INVALID_ARGUMENTS and write nothing; series-wide changes go through updateRecurring / deleteRecurring (#175).
  • updateRecurring forwards a reminders patch. A thisAndFollowing split that clears the rule keeps the occurrence's reminders on the standalone event it leaves, as Android does (#175).

Fixed #

  • updateRecurring(allEvents) switching a timed series to all-day shifts the start in local time, where EventKit puts all-day events. It shifted in the series' stored zone, so a UTC-stored series landed on UTC midnight, which west of UTC is the day before: the series gained an extra first occurrence off its BYDAY and lost its last one. The day-move check reads each date in its own zone too (#187).
  • updateRecurring and deleteRecurring with thisAndFollowing refuse a non-recurring event with INVALID_ARGUMENTS and write nothing, as Android does. A timestamp that fell inside the event matched it, so the whole event was edited or deleted and the call reported success (#124).
  • updateRecurring refuses a start move with no new rule when it breaks any day the rule pins, as Android does. It checked only BYDAY when the rule had one, so a yearly BYMONTH=11;BYDAY=4TH series could move to a Thursday in December (#188).
  • Modal plumbing (#123): a second showEventModal/showCreateEventModal while one is showing fails OPERATION_FAILED instead of orphaning the first reply; swiping the view modal down resolves it; a modal is presented from the top of the presentation stack, so an app sheet that's already up no longer swallows it; and a missing root view controller fails OPERATION_FAILED instead of crashing with fatalError.
  • createEvent refuses a recurrence rule it can't parse (a FREQ other than DAILY/WEEKLY/MONTHLY/YEARLY, or malformed input) with INVALID_ARGUMENTS and writes nothing, as updateRecurring already does. It used to drop the rule and save a one-off event (#125).
  • On iOS 18, answering a full-access prompt with Add Events Only reports writeOnly rather than notDetermined, so a createEvent fired straight after the prompt passes its permission gate. The request now waits briefly for the OS status to catch up with an ungranted answer (#137).

0.8.0 - 2026-09-26 #

Fixed #

  • updateRecurring with a new rule anchors the series on the first day that rule generates. Changing a weekly series to another weekday left its start on the old day, so the first occurrence was stranded there. A rule that generates no occurrence at all is refused with INVALID_ARGUMENTS and the series is left untouched (#140).
  • deleteCalendar refuses a calendar EventKit marks immutable or read-only with READ_ONLY before asking the store, instead of surfacing the refused remove as OPERATION_FAILED. updateCalendar now checks isImmutable too, EventKit's own flag for "can't be edited or deleted" (#126).

0.7.1 - 2026-09-21 #

Fixed #

  • A grant is honoured while EKEventStore.authorizationStatus still reports .notDetermined. On iOS 17+ the live status can lag the request handler, so the first call after a fresh grant failed its permission gate until the app restarted. The tier the OS confirmed is now remembered for the process and consulted only when it outranks a stale live status; a terminal denied / restricted always wins, so a Settings revocation is never masked (#134).
  • showCreateEventModal is gated only below iOS 17, where the editor runs in-process. On iOS 17+ EKEventEditViewController is out-of-process and needs no calendar access (#121).
  • Full-access requests on iOS 17+ check NSCalendarsFullAccessUsageDescription (what the OS demands) rather than the legacy key, and only when a prompt will actually fire — already-granted and terminal states get their status back without a configuration error (#121).
  • createEvent with a named calendarId under write-only access reports permissionDenied instead of notFound (#121).

Changed #

  • The permission stack is restructured behind a CalendarAuthorization seam: CalendarAccess normalises EventKit's status across the iOS 17 divide and PermissionService holds policy only. Swift unit tests cover the grant, revocation and configuration paths (#134).

0.7.0 - 2026-06-17 #

Added #

  • Write-only access via requestWriteOnlyAccessToEvents (iOS 17+). It is not a permanent ceiling — a later full request re-prompts and upgrades the app in-app (#89).
  • createEvent with no calendarId writes to defaultCalendarForNewEvents (#88).
  • Event reminders via EKAlarm(relativeOffset:) on EKEvent.alarms (#87).

Changed #

  • Simplified the permission request into a single iOS 17 availability dispatch (#108).

0.6.0 - 2026-06-16 #

Changed #

  • Breaking: updateRecurring takes the anchored occurrence's new start (newStartMillis) instead of startMinuteOfDay. shiftStart translates the series anchor by the wall-clock delta (calendar-day count + time-of-day) in the event's EKEvent.timeZone, so a move can change the day and time together and stays correct across DST (#103).

Behaviour #

  • updateRecurring rejects a start that moves the day of a series whose EKRecurrenceRule pins it (daysOfTheWeek / daysOfTheMonth / monthsOfTheYear) unless a new rule is also supplied (dayMoveConflictsWithRule).

0.5.2 - 2026-06-15 #

Fixed #

  • EventKit reads and writes now run on a background serial queue instead of the main thread, so operations on large calendars no longer stall the UI (#79)
  • listEvents over a span longer than EventKit's ~4-year predicate limit now chunks the query into windows and de-duplicates recurring instances across window boundaries, so long ranges return every event exactly once, sorted (#94)
  • createCalendar now fails with a clear error on sources that can't hold calendars (e.g. subscribed/holiday sources) instead of an opaque EventKit failure (#96)
  • updateRecurring(thisAndFollowing, recurrenceRule: Patch.clear()) now splits the series — truncating the master before the occurrence and detaching a standalone non-recurring event at the split point — instead of collapsing the whole series into a single event. Matches Android's behavior (#93)

0.5.1 - 2026-06-15 #

Fixed #

  • showEventModal(edit: true) no longer crashes with NSInvalidArgumentException ("Pushing a navigation controller is not supported"). EKEventEditViewController is a UINavigationController subclass and is now presented directly instead of being wrapped in another navigation controller (#77)

0.5.0 - 2026-06-11 #

Changed #

  • Breaking: recurring-edit split — updateRecurring / deleteRecurring accept only allEvents and thisAndFollowing (both EKSpan.futureEvents); single occurrences go through updateEvent / deleteEvent with an occurrence timestamp (EKSpan.thisEvent)
  • updateRecurring time changes preserve each occurrence's date, replacing only the time-of-day components

Fixed #

  • Occurrence edits validate before mutating the live EKEvent; an edit whose startDate passes the occurrence's untouched end now fails with invalidArguments (matching Android) instead of saving an inverted event

0.4.0 - 2026-05-25 #

Added #

  • updateRecurring() — series-level recurring-event edits with EventSpan (allEvents / thisAndFollowing / thisInstance), backed by EKSpan.futureEvents and EKSpan.thisEvent
  • deleteRecurring() — same EventSpan semantics on EKEventStore.remove
  • url field on events via EKEvent.url
  • Patch<T> support in updateEvent() — null leaves a field unchanged, Patch.set writes, Patch.clear nils the field on the EKEvent
  • edit flag on showEvent() — presents EKEventEditViewController instead of EKEventViewController

0.3.5 - 2026-04-20 #

Fixed #

  • Calendar source lookup fallback when default source is unavailable (#13)
  • createCalendar default fallback prefers iCloud over other CalDAV sources (#33)

0.3.4 - 2026-02-08 #

Added #

  • Swift Package Manager support (CocoaPods continues to work as before)

0.3.3 - 2025-12-21 #

Fixed #

  • Fixed parsing of instanceId for events with @ in their event ID (e.g., Google Calendar IDs like abc123@google.com)

0.3.2 - 2025-12-19 #

Changed #

  • createCalendar() signature updated to accept optional platformOptions parameter (ignored on iOS)

0.3.1 - 2025-11-07 #

Fixed #

  • showEvent() now properly stores result callback and calls it in eventViewController(_:didCompleteWith:) delegate method after modal is dismissed

0.3.0 - 2024-11-05 #

Changed #

  • BREAKING: deleteEvent() now always deletes entire series for recurring events using EKSpan.futureEvents on master event (removed deleteAllInstances parameter)
  • BREAKING: updateEvent() now always updates entire series for recurring events using EKSpan.futureEvents on master event (removed updateAllInstances parameter)
  • Native code now extracts event ID from instance ID format automatically and fetches master event

Removed #

  • BREAKING: NOT_SUPPORTED error code (no longer needed)

0.2.0 - 2024-11-05 #

Added #

  • openAppSettings() implementation using UIApplication.openSettingsURLString

Removed #

  • BREAKING: getPlatformVersion() implementation (unused boilerplate)

0.1.1 - 2024-11-04 #

Version sync with other packages. No functional changes.

0.1.0 - 2024-11-04 #

Initial release.