Streaming is available in most browsers,
and in the Developer app.
-
Discover Calendar and EventKit
Discover how you can bring Calendar into your app and help people better manage their time. Find out how to create new events from your app, fetch events, and implement a virtual conference extension. We'll also take you through some of the changes to calendar access levels that help your app stay connected without compromising the privacy of someone's calendar data.
Chapters
- 0:41 - Integrate with Calendar
- 1:52 - Frameworks overview
- 4:36 - Adding events
- 4:56 - Adding: EventKitUI
- 8:07 - Adding: Siri Event Suggestions
- 10:59 - Adding: Write-only
- 12:16 - Adding: EventKit
- 14:23 - Full Access
- 18:01 - Virtual conferences
Resources
- Accessing Calendar using EventKit and EventKitUI
- EventKit
- Explore the Human Interface Guidelines for privacy
- Siri Event Suggestions
- Universal Links for Developers
Related Videos
WWDC23
WWDC20
-
Download
♪ ♪ Adam: Hi, I'm Adam. In this video, I'll be covering how your app can help people manage their time with Calendar and EventKit. First, I'll go over some ways that apps can integrate with Calendar and give an overview of the frameworks involved. Then I'll walk through some specific examples of how to use these frameworks to implement common features, like adding events, fetching events with full access, and implementing a virtual conference extension.
People rely on their calendar to keep track of their time and plan for their future, but Calendar is more than just one app. There are different roles that apps can fill when integrating with Calendar, and when put together, these different roles provide for a richer Calendar experience. Some apps support making reservations, buying tickets, or arranging meetups. They take part by adding events. Some apps participate by displaying events, maybe for a custom calendar widget or a planner.
Other apps contribute in both directions. They help people manage their schedule by viewing and editing events.
Apps that support voice or video calls can also take part. A virtual conference extension not only improves the experience in the Calendar app, but it also provides a shortcut back into your app.
All of these fit together to provide a cohesive experience for managing time. Later, I'll walk through some specific examples to get started in each of these areas.
There are two frameworks that you can use to integrate with Calendar. The EventKit framework is used to work directly with calendar data. EventKitUI is an iOS and Mac Catalyst framework that provides view controllers for showing calendar UI in your app. Let's take a closer look at each...
Starting with some of the basic types in EventKit.
The EKEventStore is the main point of contact for your calendar data. You use an event store to request access and to fetch or save. You should only have one of these for your application. The EKEvent class represents a specific event, and it has properties like title, start date, end date, and location. Each event belongs to a calendar, which is represented by the EKCalendar class. Calendars have a title and color, which can be useful for coloring the events. Finally, each calendar account is represented by an EKSource, which is a collection of calendars. Sources are useful for grouping calendars in your UI.
EventKit is a foundational framework for interacting with calendar data. EventKitUI is built on top of EventKit to provide some useful built-in views.
There are three view controllers provided by EventKitUI. The EKEventEditViewController shows an event editor. Use this to add a new event or make changes to an existing event.
The EKEventViewController shows event details. Use this to show information in your app about an existing event. And the EKCalendarChooser shows a calendar list and supports either single selection or multiple selection. Use this to let people choose a calendar to add an event to or choose which calendars should be visible in your app.
Calendars are private, so the system prevents apps from reading or writing Calendar events without permission. There are three levels of access that an app can have for Calendar: No access, write-only access, or full access. Apps with no calendar access can add events using EventKitUI or Siri Event Suggestions. Apps with write-only access can add events directly using EventKit.
And apps with full access can fetch or modify existing events, access existing calendars, and create new calendars.
One of the most common ways to integrate with Calendar is to add new events.
Events can be added to the calendar in a few different ways. Use EventKitUI or Siri Event Suggestions to add one event at a time. Or, to save events directly, use EventKit.
The simplest way to add an event to the calendar is to let EventKitUI do most of the work. Present an EKEventEditViewController to show an editor with the event details filled out. This gives people a chance to choose a calendar or make other changes before deciding whether to save the event. In iOS 17, this UI runs in a separate process, which means you don't need to request Calendar access.
Adding an event with EventKitUI is a four-step process. First, create an event store. Next, create an event and fill in the details. Then create a view controller configured to edit the event. And finally, present the view controller.
Let's walk through this in more detail with some code. Start by creating an eventStore. Next, create an event and fill in the details. The details you set here will be used in the editor UI. Once the editor is presented, people will have a chance to make changes, but ideally, they can just tap the add button to confirm, so filling in the right details saves them time.
Every event needs a title. The title is used in many places including widgets and notifications, so keep it simple.
The most important properties are the start and end date. Use date components to make the start date.
Once you have the start date, calculate the end date by adding the duration. Use Foundation's Calendar and DateComponents types for date math, or else, you'll hit surprising results around Daylight Saving Time. Here the sample adds two hours to our start date.
If the event takes place in a specific time zone, then be sure to set that as well. The default time zone will be the current system time zone.
Set a location to let people know where the event takes place. Including a full address or using a MapKit handle will enable features like Maps suggestions and Time to Leave alerts.
Finally, add some notes to provide some extra detail.
Once you've set the event properties, the next step is to create the EKEventEditViewController. Assign the event and event store properties.
In the editor, people can either add the event or cancel. If you want to know whether they added the event, then use the delegate property and implement the EKEventEditViewDelegate protocol.
Finally, present the editor. At this point, the event isn't in the calendar yet. Tapping the Add button will save it, while tapping on cancel will dismiss the editor without saving anything.
For a more complete example of adding events with EventKitUI, check out the "DropInLessons" target in the "Accessing Calendar using EventKit and EventKitUI" sample code.
Another way to add events to the calendar is to use Siri Event Suggestions for reservations made in your app. The Siri Event Suggestions API is part of the Intents Framework. It doesn't require prompting for Calendar access, and it doesn't show any UI in your app. Instead, these events will appear in the Calendar inbox like an invitation. They can then be either added to a calendar or ignored.
Siri Event Suggestions support reservations for restaurants or hotels, travel bookings like flights or rental cars, and ticketed events like concerts or sporting events. If the reservation is later canceled or modified, the events can be updated.
Using the Siri Event Suggestions API is a four-step process. First, create an INReservation. Then, wrap the reservation in an intent and response. Next, create an INInteraction. Finally, donate the interaction to the system. Let's dive into some sample code.
A reservation requires a unique reference that the system will use to identify it. Create that reference by making an instance of INSpeakableString with a unique vocabulary identifier and a spoken phrase. The phrase can be used to refer to this reservation when talking to Siri.
Use INDateComponentsRange to set the start and end time for the reservation.
Give the event a location using the CLPlacemark type.
Then, put that all together by creating an instance of one of the subclasses of INReservation. For a restaurant reservation, use INRestaurantReservation. This initializer has a few more optional arguments that aren't shown, and each subclass has its own specific options. Check out the documentation to learn more.
The next step is to create an INGetReservationDetailsIntent with the reservation reference.
Then create an INGetReservationDetailsIntentResponse with the reservation object.
Next, create an INInteraction with the intent and response.
Finally, invoke the interaction's donate method.
This example only scratches the surface of what can be done with Siri Event Suggestions. For more information about creating Siri Event Suggestions, check out the "Broaden your reach with Siri Event Suggestions" video from WWDC20.
EventKitUI or Siri Event Suggestions provide the best experience for adding events. Use write-only access only if your app needs to show a custom editing UI, add multiple events at the same time, or add events to the calendar without user interaction.
To request write-only access, include the NSCalendarsWriteOnlyAccess UsageDescription key in your Info.plist to explain why your app needs access. This string will be shown in the request prompt. Here is the prompt for our sample application: "Save repeating lessons to a calendar that you choose." Write-only access also has some limitations. For one, people may choose not to allow access. If access is granted, then the app still cannot read any existing events from the calendar, including events added by the same app. The app also can't read the calendar list or create new calendars. Write-only access is new in iOS 17 and macOS Sonoma. To learn more about how this affects existing apps, check out the "What's new in Privacy" video.
Adding a new event with write-only access will look similar to adding a new event using EventKitUI. It starts out the same: Create an event store. Then request write-only access. If access was granted, create a new event and fill in the details. Lastly, save the event.
Let's check that out in more detail.
Start by creating an event store. Then, request write-only access by calling the requestWriteOnlyAccessToEvents method.
The return value indicates whether access was granted. People can choose to deny access, so be sure to handle that gracefully.
Access requests are most likely to be approved when people understand why your app needs access, so you should request access when someone has first interacted with a feature that requires it.
Next, create an event and fill in the details. There's another important difference here. When using EventKitUI, the details you fill in will show up in an editor, and anything not filled in gets a default value. When saving an event directly with EventKit, nothing will be filled in for you. What you set is what will be saved. There are some properties that must be filled in, or the save will fail.
One required property is the calendar. Use the event store's defaultCalendarForNewEvents property to use the calendar configured as the default in settings.
The other required properties are title, start date, and end date. Everything else is optional, but it's good to fill out as much as possible.
Once the details are filled in, save the event using the event store's save method.
To see a full example of adding events with EventKit, check out the "RepeatingLessons" target in the "Accessing Calendar using EventKit and EventKitUI" sample project.
Apps that want to add events to the calendar should use EventKitUI, Siri Event Suggestions, or write-only access. For the very few apps that need to read calendar data, there is full access.
Only request full access if your app has a core feature that requires displaying, updating, or removing existing events.
To request full access, include the NSCalendars FullAccessUsageDescription key in your Info.plist. This string will be shown in the request prompt.
Calendars contains sensitive information, and the prompt for full access describes how much data is included. It takes a lot of trust for people to allow an app to read their calendar. If people don't yet trust your app, then the request may be denied. Only request full access if it is essential to the core experience of your app, and only request at a time when it is clear why the access is needed.
If your app does need full access for a core feature, then it will probably need to fetch events. To do this, first create an event store. Next, request full access. Then, create a predicate. And finally, fetch the events from the event store.
Let's check out the code for that. As with our other examples, start by creating an event store. Apps should only have one event store, so be sure to reuse this. Next, request full access by calling the requestFullAccessToEvents method.
This will show a prompt and return whether the access was allowed. Full access prompts will be denied more often, so be sure to handle that. Once you have full access, create a predicate by calling the event store's predicateForEvents method. The predicate describes which events you want to fetch with a date range and an optional list of calendars. This code uses a range for the current month. Use the shortest range possible for the best performance. If the calendars argument is left nil, the results will include events from all calendars.
Finally, fetch the events by passing the predicate to the event store's events(matching:) method, which returns an array of matching events. The events in this array are not necessarily ordered, so sort the results if needed. If you want to play around with a full example of fetching events, then check out the "MonthlyEvents" target in the "Accessing Calendar using EventKit and EventKitUI" sample project.
To support releases earlier than iOS 17 and macOS Sonoma, perform a runtime availability check. Call the new requestAccess methods on iOS 17 and macOS Sonoma and beyond.
On earlier OSes, call the legacy requestAccess method.
Additional usage strings are required prior to iOS 17 or macOS Sonoma. Include the NSCalendarsUsageDescription key for requesting calendar access. Apps that use EventKitUI also need to include the NSContactsUsageDescription key because EventKitUI will request contacts access for the app.
If an app is missing these strings when requesting access, then it will crash.
So far we've covered a few ways to add events and how to fetch events, but working with events is not the only way to integrate with Calendar. If your app supports voice or video calls, use a virtual conference extension to let people add your calls directly to their events. There are two ways that these extensions are used.
When adding a location to an event, custom virtual conference options will appear in the location picker. This example has the options provided by the FaceTime and Skype virtual conference extensions. Tapping on one of these will add that virtual conference to the event.
An event that has a virtual conference will show a custom join option in the event details.
Making a virtual conference extension takes just a few steps. First, create a new Virtual Conference Extension target in Xcode. Then, there are two methods in the extension protocol to implement. Implement fetchAvailableRoomTypes to provide your available room types, and then implement fetchVirtualConference to provide a virtual conference object for a selected room type.
Let's walk through an example.
First, create a Virtual Conference extension target in Xcode. The new target will have a stubbed subclass of EKVirtualConferenceProvider.
The first method to override is fetchAvailableRoomTypes. The room types are shown in the location picker.
Choose a title for each room type. This will be shown in the UI next to your app icon.
Also choose a unique identifier for each room type. The identifier is used to let your extension know which room type was chosen.
Create an instance of EKVirtualConferenceRoomTypeDescriptor using the title and identifier. If your app supports multiple room types, then make an instance for each one.
Finally, return an array of your room types.
The next method to implement is fetchVirtualConference. This is called when one of the room types is selected. The identifier argument tells you which room was selected.
Virtual conferences have one or more URL descriptors, which tell Calendar how to join.
Create an EKVirtualConferenceURLDescriptor with the URL to open and an optional title.
Use Universal Links for your URL to allow your app to be opened directly.
The title helps to distinguish between multiple join options. Here it's not needed because there's only one way to join.
Provide any additional information in a details string. This text will be included in a special virtual conference section of the event details UI.
Finally, put all of this together to create and return an EKVirtualConferenceDescriptor. The title here helps distinguish between multiple room types. This example only has one room type, so the title is left nil.
With just these two methods, your app will appear in the Calendar app's location picker as an option for virtual conferences.
Now that we've covered a few different ways of integrating with Calendar, think about how your app can contribute. Use EventKitUI or Siri Event Suggestions to add events without requesting access. If you do need to request access, request the minimum access needed, and only when it's needed. And if you have a voice or video call app, then implement a virtual conference extension. I can't wait to see how your app integrates with Calendar. Thanks for watching. ♪ ♪
-
-
5:49 - Adding an event with EventKitUI
// Create an event store let store = EKEventStore() // Create an event let event = EKEvent(eventStore: store) event.title = "WWDC23 Keynote" let startDateComponents = DateComponents(year: 2023, month: 6, day: 5, hour: 10) let startDate = Calendar.current.date(from: startDateComponents)! event.startDate = startDate event.endDate = Calendar.current.date(byAdding: .hour, value: 2, to: startDate)! event.timeZone = TimeZone(identifier: "America/Los_Angeles") event.location = "1 Apple Park Way, Cupertino, CA, United States" event.notes = "Kick off an exhilarating week of technology and community." // Create a view controller let eventEditViewController = EKEventEditViewController() eventEditViewController.event = event eventEditViewController.eventStore = store eventEditViewController.editViewDelegate = self // Present the view controller present(eventEditViewController, animated: true)
-
9:17 - Siri Event Suggestions
// Create an INReservation let spokenPhrase = “Lunch at Caffè Macs” let reservationReference = INSpeakableString(vocabularyIdentifier: "df9bc3f5", spokenPhrase: spokenPhrase, pronunciationHint: nil) let duration = INDateComponentsRange(start: myEventStart, end: myEventEnd) let location = CLPlacemark(location: myCLLocation, name: "Caffè Macs", postalAddress: myAddress) let reservation = INRestaurantReservation(itemReference: reservationReference, reservationStatus: .confirmed, reservationHolderName: "Jane Appleseed", reservationDuration: duration, restaurantLocation: location) // Create an intent and response let intent = INGetReservationDetailsIntent(reservationContainerReference: reservationReference) let intentResponse = INGetReservationDetailsIntentResponse(code: .success, userActivity: nil) intentResponse.reservations = [reservation] // Create an INInteraction let interaction = INInteraction(intent: intent, response: intentResponse) // Donate the interaction to the system interaction.donate()
-
12:41 - Adding an event with write-only access
// Create an event store let store = EKEventStore() // Request write-only access guard try await store.requestWriteOnlyAccessToEvents() else { return } // Create an event let event = EKEvent(eventStore: store) event.calendar = store.defaultCalendarForNewEvents event.title = "WWDC23 Keynote" event.startDate = myEventStartDate event.endDate = myEventEndDate event.timeZone = TimeZone(identifier: "America/Los_Angeles") event.location = "1 Apple Park Way, Cupertino, CA, United States" event.notes = "Kick off an exhilarating week of technology and community." // Save the event guard try eventStore.save(event, span: .thisEvent) else { return }
-
15:51 - Fetch events
// Create an event store let store = EKEventStore() // Request full access guard try await store.requestFullAccessToEvents() else { return } // Create a predicate guard let interval = Calendar.current.dateInterval(of: .month, for: Date()) else { return } let predicate = store.predicateForEvents(withStart: interval.start, end: interval.end, calendars: nil) // Fetch the events let events = store.events(matching: predicate) let sortedEvents = events.sorted { $0.compareStartDate(with: $1) == .orderedAscending }
-
19:18 - Virtual conference extension
// Create the extension target class VirtualConferenceProvider: EKVirtualConferenceProvider { // Provide the room types override func fetchAvailableRoomTypes() async throws -> [EKVirtualConferenceRoomTypeDescriptor] { let title = "My Room" let identifier = "my_room" let roomType = EKVirtualConferenceRoomTypeDescriptor(title: title, identifier: identifier) return [roomType] } // Provide the virtual conference override func fetchVirtualConference(identifier: EKVirtualConferenceRoomTypeIdentifier) async throws -> EKVirtualConferenceDescriptor { let urlDescriptor = EKVirtualConferenceURLDescriptor(title: nil, url: myURL) let details = "Enter the meeting code 12345 to enter the meeting." return EKVirtualConferenceDescriptor(title: nil, urlDescriptors: [urlDescriptor], conferenceDetails: details) } }
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.