●FUNDING — Rork's $15M seed was led by Left Lane Capital with Peak XV, True Ventures, Goodwater, and a16z Speedrun●GROWTH — Rork keeps growing with 743K monthly visits and an 85% growth rate●MAX — Rork Max generates native Swift apps for iPhone, iPad, Watch, TV, Vision Pro, and iMessage●MAX — It reaches HealthKit, Core ML, and Dynamic Island — territory React Native struggles with●MARKET — Apple pushes agentic coding in Xcode 27, accelerating AI-driven native development●MARKET — Gartner projects 75% of new apps will be low-code or no-code by the end of 2026●FUNDING — Rork's $15M seed was led by Left Lane Capital with Peak XV, True Ventures, Goodwater, and a16z Speedrun●GROWTH — Rork keeps growing with 743K monthly visits and an 85% growth rate●MAX — Rork Max generates native Swift apps for iPhone, iPad, Watch, TV, Vision Pro, and iMessage●MAX — It reaches HealthKit, Core ML, and Dynamic Island — territory React Native struggles with●MARKET — Apple pushes agentic coding in Xcode 27, accelerating AI-driven native development●MARKET — Gartner projects 75% of new apps will be low-code or no-code by the end of 2026
Adding Events to the User's Calendar from a Rork Max App: EventKit's Three-Tier Permissions and Write-Only Access
Since iOS 17, EventKit offers three access tiers: a permission-free UI, write-only, and full access. Here is how to ship calendar and reminders integration in a Rork Max app without tanking your opt-in rate.
The Weight of One Sentence: "Access All Your Calendars"
While prototyping an "add this to your calendar" button for a habit-building feature, I got the code working in half a day — and then watched it die in testing. When I asked the few dozen TestFlight testers why they skipped the feature, nearly all of them pointed at the same thing: the full-access dialog that says the app wants access to all events on all calendars. For a feature that only ever writes one event, that sentence reads as wildly disproportionate. In my tally at the time, the grant rate was below fifty percent.
The fix came from rereading what iOS 17 did to EventKit. After switching to write-only access and moving one flow to the permission-free system UI, completion of the same feature in the same test group recovered to over eighty percent. As an indie developer I have learned that a single permission sheet can decide whether a feature lives or dies. This article walks through the three EventKit access tiers with a Rork Max–generated native Swift app: working code, the trade-offs, and the App Review angles.
One scoping note: this is not about building your own calendar screen inside the app. For that, see the beginner-friendly tutorial on building a calendar and schedule app with Rork. Here we integrate with the calendar and reminders the user already lives in.
iOS 17 Split EventKit into Three Tiers
Up to iOS 16, calendar access was all-or-nothing behind a single NSCalendarsUsageDescription. For apps built against the iOS 17 SDK, there are now three levels.
Access tier
What you can do
Required Info.plist key
Permission dialog
No permission (EventKitUI)
Add events through the system edit sheet; your app never touches calendar data
None
Never shown
Write-only
Save events directly from code; reading existing events is impossible
NSCalendarsWriteOnlyAccessUsageDescription
Light wording, explicitly "add events only"
Full access
Read, search, edit, and delete events
NSCalendarsFullAccessUsageDescription
Heavy wording about all events on all calendars
There is one trap worth flagging first. A binary built with the iOS 17 SDK ignores the legacy NSCalendarsUsageDescription when the new APIs request access, and the app crashes at runtime for a missing key. When I asked Rork Max to simply "add calendar integration," it occasionally generated the old key — presumably an artifact of training data — and I hit This app has crashed because it attempted to access privacy-sensitive data without a usage description in the simulator once myself. Always verify the generated Info.plist uses the new keys.
Reminders moved to NSRemindersFullAccessUsageDescription in the same release. But as we will see, reminders got no write-only tier, and that asymmetry ends up shaping the design.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦You can redesign a calendar feature that users were abandoning at the permission dialog into a flow that adds events with no permission at all
✦You will be able to pick the right EventKit access tier introduced in iOS 17 and avoid App Review rejections caused by weak purpose strings
✦You get working Swift code, from duplicate-event checks to reminder creation, that you can drop into your app today
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Adding Events with Zero Permission: EKEventEditViewController
The first option to consider is the flow that requests nothing. Since iOS 17, EventKitUI's EKEventEditViewController can be presented without any calendar permission. The edit sheet is rendered out-of-process by the system, and whatever the user saves is never readable by your app — that is what makes the permission unnecessary. No dialog means the drop-off point simply does not exist.
To use it from SwiftUI, wrap it in UIViewControllerRepresentable:
import SwiftUIimport EventKitUI// Permission-free wrapper around the system event editorstruct EventEditSheet: UIViewControllerRepresentable { let title: String let startDate: Date let endDate: Date let notes: String @Environment(\.dismiss) private var dismiss func makeUIViewController(context: Context) -> EKEventEditViewController { let store = EKEventStore() // never call a request-access API let event = EKEvent(eventStore: store) event.title = title event.startDate = startDate event.endDate = endDate event.notes = notes let vc = EKEventEditViewController() vc.eventStore = store vc.event = event // pre-filling initial values is allowed vc.editViewDelegate = context.coordinator return vc } func updateUIViewController(_ vc: EKEventEditViewController, context: Context) {} func makeCoordinator() -> Coordinator { Coordinator(dismiss: dismiss) } final class Coordinator: NSObject, EKEventEditViewDelegate { let dismiss: DismissAction init(dismiss: DismissAction) { self.dismiss = dismiss } func eventEditViewController(_ controller: EKEventEditViewController, didCompleteWith action: EKEventEditViewAction) { dismiss() // close on .saved or .canceled } }}
Present it with .sheet. Because you can pre-fill the title and dates, the user's entire job is to glance at the content and tap Add — two gestures.
When prompting Rork Max, naming the exact structure pays off. This is the instruction that worked for me verbatim:
Add an "Add to Calendar" button on the event detail screen.Implement it by wrapping EventKitUI's EKEventEditViewController in aUIViewControllerRepresentable and presenting it as a sheet. Do NOT callrequestFullAccessToEvents or requestWriteOnlyAccessToEvents on EKEventStore.Do NOT add any calendar usage description keys to Info.plist.
Spelling out which APIs must not be called produces far more repeatable generations than a vague "implement it without asking for permission."
Silent Event Creation with Write-Only Access
Sometimes routing every save through a system sheet is too much friction — say, automatically adding an event the moment a booking completes, or writing several events in one batch. That is what iOS 17's write-only tier, requestWriteOnlyAccessToEvents(), is for.
import EventKitenum CalendarWriter { static let store = EKEventStore() // Confirm write-only access, then save the event static func addEvent(title: String, start: Date, end: Date, alarmMinutesBefore: Int? = nil) async throws -> Bool { var status = EKEventStore.authorizationStatus(for: .event) if status == .notDetermined { _ = try await store.requestWriteOnlyAccessToEvents() status = EKEventStore.authorizationStatus(for: .event) } // Both .writeOnly and .fullAccess allow saving guard status == .writeOnly || status == .fullAccess else { return false } let event = EKEvent(eventStore: store) event.title = title event.startDate = start event.endDate = end event.calendar = store.defaultCalendarForNewEvents if let minutes = alarmMinutesBefore { event.addAlarm(EKAlarm(relativeOffset: TimeInterval(-minutes * 60))) } try store.save(event, span: .thisEvent) return true }}
Add NSCalendarsWriteOnlyAccessUsageDescription to Info.plist. The resulting dialog says, in effect, "this app can add events but cannot read them" — and the perceived weight is completely different. As mentioned up top, my grant rate went from under half with full access to the low eighties after switching to write-only. A small sample, but more than enough to confirm that the dialog's wording is what moves the number.
The constraint is equally clear: with write-only access you cannot read anything, so you cannot check whether you already created the same event. The practical workaround for double-taps and repeat visits is to store the saved event.eventIdentifier in UserDefaults as your own "already added" flag. Comparing identifiers you saved yourself requires no read permission.
Full Access Is Needed Less Often Than You Think
requestFullAccessToEvents() is only justified when reading, searching, or updating existing events is part of the requirement — typically features like "compute the user's free slots and suggest times" or reconciling against externally created events.
// Full access required: fetch the next 7 days of events for duplicate checksstatic func fetchUpcoming(days: Int = 7) async throws -> [EKEvent] { guard try await store.requestFullAccessToEvents() else { return [] } let start = Date() let end = Calendar.current.date(byAdding: .day, value: days, to: start)! let predicate = store.predicateForEvents(withStart: start, end: end, calendars: nil) return store.events(matching: predicate)}
My rule of thumb is blunt: do I need to render the user's existing events on screen? If not, full access is not needed. Defaulting to full access "just in case" makes the review wording heavier, drags the grant rate down, and buys you nothing. Escalating the tier after the requirement is confirmed is never too late.
Reminders Have No Write-Only Tier
Deadline-driven tasks often fit the standard Reminders app better than the calendar. But EventKit offers no write-only tier for reminders — requestFullAccessToReminders() is the only option.
// Reminders are full-access only. Defer the request until the moment of usestatic func addReminder(title: String, due: DateComponents) async throws -> Bool { let store = EKEventStore() guard try await store.requestFullAccessToReminders() else { return false } let reminder = EKReminder(eventStore: store) reminder.title = title reminder.dueDateComponents = due // e.g., specify down to hour and minute reminder.calendar = store.defaultCalendarForNewReminders() reminder.addAlarm(EKAlarm(absoluteDate: Calendar.current.date(from: due)!)) try store.save(reminder, commit: true) return true}
Given the asymmetry, decide which integration is primary. In this situation I prefer making the calendar (write-only) the main path and offering reminders as a secondary option. If your app's core is task management instead, it is worth inserting a one-screen explanation right before the first reminders request. The pre-permission priming approach I described for designing ATT pre-permission priming transfers to this dialog unchanged.
App Review and Purpose Strings: Avoiding the Rejection
In my experience, EventKit review feedback concentrates almost entirely on Guideline 5.1.1 — purpose strings that are not specific enough. Three points cover it:
Put the "what" and the "why" in one sentence. "This app uses your calendar" will not pass. Write something like "Used to add the start time of classes you book as an event on your calendar" — name what you write and why.
Request at the moment of use, never at launch. Batch-requesting permissions on first launch reads poorly no matter how polite the strings are, and it measurably lowers grant rates. Request when the button is tapped.
Keep the privacy label consistent. If event content never leaves the device, you do not declare calendar data collection in App Store Connect; if it does reach your server, a missing declaration is an instant rejection risk. A design that stays within write-only access also keeps this declaration minimal — another quiet advantage.
The same "step the permission down" philosophy appeared in iOS 18's contacts flow; reading this alongside the iOS 18 contact access button and limited-access design makes the multi-year trend visible: Apple is steadily replacing all-or-nothing prompts with graduated, minimal-scope tiers.
Wrapping Up: Start from the Permission-Free Path
The evaluation order that held up best for me — for grant rate, review, and implementation cost alike — is top-down: EKEventEditViewController (no permission) → write-only → full access. As a concrete first step, drop the EventEditSheet above into an existing detail screen and watch an event land on the calendar with no dialog at all. The absence of that one sheet changes how the feature gets used.
Share
Thank You for Reading
Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.