Swift EventKit: Calendar management

With Swift EventKit framework it provides powerful capabilities for working with calendars, allowing developers to seamlessly create, edit, fetch and delete calendar events within their applications. 

In this blog post, we will explore a full example of eventkit to create, edit, and delete calendar events, helping users stay organized and on top of their schedules. 

TLDR: If you just want the code, scroll to the bottom ðŸ™‚ 

Setup project

Before we start diving into working with the native calendar we have to setup a basic project. We will create a SwiftUI project with one view and one viewmodel. The view will contain 4 buttons: Create event, Delete event, Get all events, Get one event and Update event. And the viewmodel will contain the functions that do the actual work.

SwiftUI file view:

struct EventKitContent: View {
    @StateObject var viewmodel = EventKitContentViewModel()
    var body: some View {
        VStack {
            Button {
                // viewmodel.createCalendarEvent()
            }label: {
                Text("Create event")
            }
            
            Button {
                // viewmodel.deleteEventById(eventId: viewmodel.createdEvent?.eventIdentifier ?? "")
            }label: {
                Text("Delete event")
            }
            
            Button {
                // viewmodel.getAllEventsForNextMonth()
            }label: {
                Text("Get all events")
            }
            
            Button {
                // viewmodel.getEventByID(eventId: viewmodel.createdEvent?.eventIdentifier ?? "")
            }label: {
                Text("Get ONE event")
            }
            
            Button {
                // viewmodel.editEventById(evendId: viewmodel.createdEvent?.eventIdentifier ?? "")
            }label: {
                Text("Update event to last all day")
            }
        }
        
    }

}

Swift file — viewmodel:

extension EventKitContent {
    @MainActor class EventKitContentViewModel: ObservableObject {
        func requestAccessToCalender() async{
        }
        
        func createCalendarEvent() async {
        }
        
        func deleteEventById(eventId: String) async {
        }
        
        func getAllEventsForNextMonth() async {
        }
        
        func getEventByID(eventId: String) async -> EKEvent? {
              return nil
        }
        
        func editEventById(evendId: String) async {
        }
    }
}

And you will now have a view looking like this:

Now we set up the basics of our project we can begin to do some real calendar work.

Swift EventKit the basics

First thing we need to do is to have the right permission to access the users calendar. This is done in your plist — if you don’t have a plist, go to your app Target-> Info-> Custom macOS Application Target Properties. There you want to add NSCalenderUsageDescription and NSCalendarsFullAccessUsageDescription write a great description for why you want to use the calendar.

In EventKitContentViewModel.swift start by importing EventKit and create a variable containing EKEventStore() and create a published variable called createdEvent.

Your viewmodel will look like this:

import EventKit

extension EventKitContent {
    @MainActor class EventKitContentViewModel: ObservableObject {
        private let eventStore = EKEventStore()
        private var accessToCalender: Bool?
        @Published var createdEvent: EKEvent?

        func requestAccessToCalender() async{
        }

        func createCalendarEvent() async {
        }
        
        func deleteEventById(eventId: String) async {
        }
        
        func getAllEventsForNextMonth() async {
        }
        
        func getEventByID(eventId: String) async -> EKEvent? {
              return nil
        }
        
        func editEventById(evendId: String) async {
        }
    }
}

Now that we are all set, let’s start working with the calendar, so let’s start by creating the function that creates a new calendar event aka. the createCalenderEvent() function.

Swift EventKit request access – also iOS 17

The first function we will create is the requestAccessToCalender. Inside this function we will create the logtic to request access to the users calender.

As of iOS17 Apple have createde a new way yo request access to the calender therefore, if your application supports anything before iOS17, you need to handle that.

In the following we will create a function that requets access to the users calender in both iOS17+ and anything before:

func requestAccessToCalender() async{
    do {
        if #available(iOS 17, *) {
            let access = try await eventStore.requestFullAccessToEvents()
            accessToCalender = access
        } else {
            let access = try await eventStore.requestAccess(to: .event)
            accessToCalender = access
        }
    }catch {
        accessToCalender = false
    }
}

We will then use this function in every function that works with the calender.

Create a new calendar event in Swift

Creating a calendar event is a straightforward process using EventKit. We already have a instance of EKEventStore (the variable called eventStore we created before), so there is only 4 simple steps left to do: 

  1. Request access to the user’s calendars. 
  2. Create a calendar event.
  3. Set what calendar to use (we will use the system default).
  4. Save the calendar event in the native calendar.
  5. Save the calendar event in app (**)

**[In this tutorial we will save the created calendar event in the createdEvent variable because we need to unique id in order for us to work with the event later — for a real app you can save the id in UserDefaults, use a custom API or something else to store the id.]**

Here is the code for function createCalendarEvent in EventKitContentViewModel.swift:

func createCalendarEvent() async {
    if accessToCalender == nil {
        await requestAccessToCalender()
    }
    // STEP 1
    if accessToCalender != nil && accessToCalender! {
        // STEP 2
        let event = EKEvent(eventStore: self.eventStore)
        event.title = "The guide worked 😮"
        event.startDate = Date()
        event.endDate = Date().addingTimeInterval(7200)
        
        event.notes = "Follow more guides on softwareanders.com and create more apps."
        
        // STEP 3
        event.calendar = self.eventStore.defaultCalendarForNewEvents
        
        // STEP 4
        do {
            try self.eventStore.save(event, span: .thisEvent)
            
            self.createdEvent = event
        } catch {
            print("Error saving event: \(error.localizedDescription)")
        }
    } else {
        print("Access to calendar not granted or an error occurred.")
    }
}

Now go to the view EventKitContent.swift and uncomment the line where we use createCalendarEvent:

Button {
     Task {
        await viewmodel.createCalendarEvent()
     }
}label: {
    Text("Create event")
}

When you click the button Create event the first thing that will happen is you will be prompt to allow access to the users calendar:

And if you click OK and allows the app access you can go to the native calendar and you will see the following event:

Delete a calendar event with EventKit

Next we will create a function the deletes a event in the users native calendar by unique id.

func deleteEventById(eventId: String) async {
    if accessToCalender == nil {
        await requestAccessToCalender()
    }
    
    if accessToCalender != nil && accessToCalender! {
        if let eventToDelete = eventStore.event(withIdentifier: eventId) {
            do {
                try eventStore.remove(eventToDelete, span: .thisEvent)
            } catch {
                print("Error deleting event: \(error.localizedDescription)")
            }
        }
    }
}

Now go to the view EventKitContent.swift and uncomment the line where we use deleteEventById:

Button {
    Task {
        await viewmodel.deleteEventById(eventId: viewmodel.createdEvent?.eventIdentifier ?? "")
    }
}label: {
    Text("Delete event")
}

When you click the Delete event button then the event will be removed from the native calendar.

Fetch all calendar events

Fetching all events from the user’s calendar allows for displaying and managing them effectively and thankfully it is easy to fetch them all.

We will be fetching all events from the native calendar for the next month:

func getAllEventsForNextMonth() async {
    if accessToCalender == nil {
        await requestAccessToCalender()
    }
    if accessToCalender != nil && accessToCalender! {
        let startDate = Date()
        let endDate = Calendar.current.date(byAdding: .month, value: 1, to: startDate)!
        
        let eventsPredicate = eventStore.predicateForEvents(withStart: startDate, end: endDate, calendars: nil)
        
        let allEvents = eventStore.events(matching: eventsPredicate)
        dump(allEvents)
    }
}

Now go to the view EventKitContent.swift and uncomment the line where we ude getAllEventsForNextMonth:

Button {
    Task {
        await viewmodel.getAllEventsForNextMonth()
    }
}label: {
    Text("Get all events")
}

And now when you click the Get all events button you will fetch all the events and dump them to the Xcode console.

Fetch one native calendar event

Sometimes you need the ability to fetch one specific calendar event for example in order to edit the name of the event.

Here we are fetching one specific calendar event based on it’s unique id:

func getEventByID(eventId: String) async -> EKEvent? {
    if accessToCalender == nil {
        await requestAccessToCalender()
    }
    if accessToCalender != nil && accessToCalender! {
        if let event = eventStore.event(withIdentifier: eventId) {
            return event
        } else {
            print("Event with identifier not found.")
        }
        return nil
    }
    
    return nil
}

Go to EventKitContent.swift and uncomment the line where we execute getEventById:

Button {
    Task {
        await viewmodel.getEventByID(eventId: viewmodel.createdEvent?.eventIdentifier ?? "")
    }
}label: {
    Text("Get ONE event")
}

Edit a existing calendar event

In order to edit a calendar event you will need the unique id and since we just made a function getting that, we simply gonna use the function:

func editEventById(evendId: String) async {
    if accessToCalender == nil {
        await requestAccessToCalender()
    }
    if accessToCalender != nil && accessToCalender! {
        if let eventToEdit = await getEventByID(eventId: evendId) {
            eventToEdit.isAllDay = true
            do {
                try eventStore.save(eventToEdit, span: .thisEvent)
            } catch {
                print("Error updating event: \(error.localizedDescription)")
            }
            
        }
    }
}

Go to EventKitContent.swift and uncomment the line where we execute editEventById:

Button {
    Task {
        await viewmodel.editEventById(evendId: viewmodel.createdEvent?.eventIdentifier ?? "")
    }
}label: {
    Text("Update event to last all day")
}

Conclusion

EventKit is a powerful framework that simplifies calendar management within Swift applications. In this blog post, we created how to create, edit, fetch all events, fetch a specific event, and delete calendar items. Remember to handle permissions and errors gracefully to ensure a seamless user experience.

Here you have all the code provied in the guide on how to work with the native calendar.

EventKitContent.swift:

import SwiftUI

struct EventKitContent: View {
    @StateObject var viewmodel = EventKitContentViewModel()
    var body: some View {
        VStack {
            Button {
                Task {
                    await viewmodel.createCalendarEvent()
                }
            }label: {
                Text("Create event")
            }
            
            Button {
                Task {
                    await viewmodel.deleteEventById(eventId: viewmodel.createdEvent?.eventIdentifier ?? "")
                }
            }label: {
                Text("Delete event")
            }
            
            Button {
                Task {
                    await viewmodel.getAllEventsForNextMonth()
                }
            }label: {
                Text("Get all events")
            }
            
            Button {
                Task {
                    await viewmodel.getEventByID(eventId: viewmodel.createdEvent?.eventIdentifier ?? "")
                }
            }label: {
                Text("Get ONE event")
            }
            
            Button {
                Task {
                    await viewmodel.editEventById(evendId: viewmodel.createdEvent?.eventIdentifier ?? "")
                }
            }label: {
                Text("Update event to last all day")
            }
        }
        
    }

}

struct EventKitContent_Previews: PreviewProvider {
    static var previews: some View {
        EventKitContent()
    }
}

EventKitContentViewModel.swift:

import EventKit

extension EventKitContent {
    @MainActor class EventKitContentViewModel: ObservableObject {
        private let eventStore = EKEventStore()
        private var accessToCalender: Bool?
        @Published var createdEvent: EKEvent?
        
        func requestAccessToCalender() async{
            do {
                if #available(iOS 17, *) {
                    let access = try await eventStore.requestFullAccessToEvents()
                    accessToCalender = access
                } else {
                    let access = try await eventStore.requestAccess(to: .event)
                    accessToCalender = access
                }
            }catch {
                accessToCalender = false
            }
        }
        
        func createCalendarEvent() async {
            if accessToCalender == nil {
                await requestAccessToCalender()
            }
            // STEP 1
            if accessToCalender != nil && accessToCalender! {
                // STEP 2
                let event = EKEvent(eventStore: self.eventStore)
                event.title = "The guide worked 😮"
                event.startDate = Date()
                event.endDate = Date().addingTimeInterval(7200)
                
                event.notes = "Follow more guides on softwareanders.com and create more apps."
                
                // STEP 3
                event.calendar = self.eventStore.defaultCalendarForNewEvents
                
                // STEP 4
                do {
                    try self.eventStore.save(event, span: .thisEvent)
                    
                    self.createdEvent = event
                } catch {
                    print("Error saving event: \(error.localizedDescription)")
                }
            } else {
                print("Access to calendar not granted or an error occurred.")
            }
        }
        
        func deleteEventById(eventId: String) async {
            if accessToCalender == nil {
                await requestAccessToCalender()
            }
            
            if accessToCalender != nil && accessToCalender! {
                if let eventToDelete = eventStore.event(withIdentifier: eventId) {
                    do {
                        try eventStore.remove(eventToDelete, span: .thisEvent)
                    } catch {
                        print("Error deleting event: \(error.localizedDescription)")
                    }
                }
            }
        }
        
        func getAllEventsForNextMonth() async {
            if accessToCalender == nil {
                await requestAccessToCalender()
            }
            if accessToCalender != nil && accessToCalender! {
                let startDate = Date()
                let endDate = Calendar.current.date(byAdding: .month, value: 1, to: startDate)!
                
                let eventsPredicate = eventStore.predicateForEvents(withStart: startDate, end: endDate, calendars: nil)
                
                let allEvents = eventStore.events(matching: eventsPredicate)
                dump(allEvents)
            }
        }
        
        func getEventByID(eventId: String) async -> EKEvent? {
            if accessToCalender == nil {
                await requestAccessToCalender()
            }
            if accessToCalender != nil && accessToCalender! {
                if let event = eventStore.event(withIdentifier: eventId) {
                    return event
                } else {
                    print("Event with identifier not found.")
                }
                return nil
            }
            
            return nil
        }
        
        func editEventById(evendId: String) async {
            if accessToCalender == nil {
                await requestAccessToCalender()
            }
            if accessToCalender != nil && accessToCalender! {
                if let eventToEdit = await getEventByID(eventId: evendId) {
                    eventToEdit.isAllDay = true
                    do {
                        try eventStore.save(eventToEdit, span: .thisEvent)
                    } catch {
                        print("Error updating event: \(error.localizedDescription)")
                    }
                    
                }
            }
        }
    }
}
Scroll to Top