
Getting access to the user’s calendar
Learn how to request access and efficiently interact with a user’s calendar and reminder data.
To work with the calendar and reminders of a user, you can use the native Apple framework EventKit
. It provides all the tools you need to create events, fetch events and much more.
The calendar information of a user is considered private information and needs to be handled with care. Because of that, the first step to be able to work with events and reminders is to get users’ authorization to work with them.
To manage the calendar on a device, you must first gather the proper permissions in your app by asking the user for them. Depending on the type of access the user provides to your app, it will have access to APIs to handle calendar events, which may include events created by other apps.
The first step is to configure the message that will be shown when requesting access, which is done on the Info tab in your project settings, with the following permission keys:
Privacy - Calendars Full Access Usage Description
Privacy - Reminders Full Access Usage Description
The element that handles all access operations to the calendar and reminder data is the EKEventStore
.
private let store = EKEventStore()
To request access to the user’s events and reminders, use the following methods:
You can also request write-only access to the events of the calendar using requestWriteOnlyAccessToEvents(completion:)
if your application features creating calendar events, but doesn’t rely on reading the user’s calendar (like a train ticket application that adds a train ride to the calendar once the user buys a ticket).
try await store.requestFullAccessToEvents()
try await store.requestFullAccessToReminders()
The current authorization status is provided by the method authorizationStatus(for:)
, which communicates the current level of access with a EKAuthorizationStatus
value.
fullAccess
: read and write access to events or reminderswriteOnly
: write-only access to events or remindersdenied
: the user has explicitly denied access to events or reminders by the appnotDetermined
: the user hasn’t chosen yet if the app should have access to events or remindersrestricted
: the app is not authorized to access the events or reminders
One way to manage the user’s permission to access calendar events and reminders is to have a dedicated class for this purpose. Here is an example of a view model class that tracks the app’s current access status to the calendar and handles requests accordingly.
import EventKit
import Observation
@Observable @MainActor
class CalendarPermissionsViewModel {
// Represents the access status for calendar and reminder permissions
enum AccessStatus {
case authorized, denied, notDetermined
}
// Published properties to track current access status
var calendarAccessStatus: AccessStatus = .notDetermined
var reminderAccessStatus: AccessStatus = .notDetermined
private let store = EKEventStore()
// Requests calendar access and updates the access status accordingly
func requestAccessToCalendar() async {
await requestAccess(for: .event) { granted in
self.calendarAccessStatus = granted ? .authorized : .denied
}
}
// Requests reminder access and updates the access status accordingly
func requestAccessToReminders() async {
await requestAccess(for: .reminder) { granted in
self.reminderAccessStatus = granted ? .authorized : .denied
}
}
// Checks and updates both calendar and reminder authorization statuses
func updateCurrentAccessStatuses() {
calendarAccessStatus = convertToAccessStatus(EKEventStore.authorizationStatus(for: .event))
reminderAccessStatus = convertToAccessStatus(EKEventStore.authorizationStatus(for: .reminder))
}
// Handles the logic for requesting access to the specified entity type
func requestAccess(for entity: EKEntityType, completion: @escaping (Bool) -> Void) async {
do {
let granted: Bool
switch entity {
case .event:
granted = try await store.requestFullAccessToEvents()
case .reminder:
granted = try await store.requestFullAccessToReminders()
default:
granted = false
}
completion(granted)
} catch {
completion(false)
print("Error requesting access to \(entity): \(error.localizedDescription)")
}
}
// Converts the system authorization status to the local AccessStatus enum
func convertToAccessStatus(_ status: EKAuthorizationStatus) -> AccessStatus {
switch status {
case .authorized:
return .authorized
case .denied, .restricted:
return .denied
default:
return .notDetermined
}
}
}
And here is how to integrate the CalendarPermissionsViewModel
with the user interface, using SwiftUI, to display the current status of each permission:
import SwiftUI
struct ContentView: View {
@State private var permissionManager = CalendarPermissionsViewModel()
var body: some View {
NavigationStack {
Form {
Section("Calendar Permission") {
HStack {
LabeledContent("Status:") {
Text(permissionManager.calendarAccessStatus == .authorized ? "Authorized ✅" : "Not Allowed ❌")
.foregroundColor(permissionManager.calendarAccessStatus == .authorized ? .green : .red)
}
}
Button("Request Access") {
Task {
await permissionManager.requestAccessToCalendar()
}
}
}
Section("Reminders Permission") {
HStack {
LabeledContent("Status:") {
Text(permissionManager.reminderAccessStatus == .authorized ? "Authorized ✅" : "Not Allowed ❌")
.foregroundColor(permissionManager.reminderAccessStatus == .authorized ? .green : .red)
}
}
Button("Request Access") {
Task {
await permissionManager.requestAccessToReminders()
}
}
}
}
.navigationTitle("App's Permissions")
.onAppear {
permissionManager.updateCurrentAccessStatuses()
}
}
}
}
To test the code, the application must be run on the Simulator or a device, as access to the calendar and reminders depends on system permissions.