Getting access to the user’s calendar

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 reminders
  • writeOnly: write-only access to events or reminders
  • denied: the user has explicitly denied access to events or reminders by the app
  • notDetermined: the user hasn’t chosen yet if the app should have access to events or reminders
  • restricted: 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.