React Native iOS Calendar Integration with an Objective-C Native Module

Giulio Milani
6 min readAug 3, 2023

--

Photo by Towfiqu barbhuiya on Unsplash

Introduction:

In this tutorial, we will explore how to integrate the native iOS calendar into a React Native app using Objective-C. By creating a custom native module, we can seamlessly connect the app with the user’s calendar, enabling them to view and add events directly from the app. Let’s get started!

Prerequisites:

Before diving into this tutorial, make sure you have the following prerequisites:

  • Basic knowledge of React Native and JavaScript.
  • Xcode installed on your development machine.
  • Familiarity with Objective-C.

Step 1: Setting up the React Native Project

To begin, let’s create a new React Native project. Open your terminal and run the following command:

npx react-native init CalendarIntegrationApp

Navigate to the project directory:

cd CalendarIntegrationApp

Step 2: Creating the Native Module

In the project root, we have a folder called ios, navigate into it:

cd ios

Generate a new Objective-C file for our native module, for this the simplest method is to open Xcode and generate the files directly from it:

Open Xcode by running the command:

xed .

Now, for the .m file:

  1. From the left sidebar, right-click on the project folder (not on the project settings) and select “New File.”
  2. Then, choose “Objective-C File.”
  3. In the file name section, enter “CalendarModule.”
  4. Click on “Next” and make sure that the project’s target is selected in the “Targets” section.
  5. Click on “Create.”

Now, let’s create the .h file:

  1. From the left sidebar, right-click on the project folder and select “New File.”
  2. Then, choose “Header File.”
  3. In the file name section, enter “CalendarModule.”
  4. Click on “Next” and make sure that the project’s target is selected in the “Targets” section.
  5. Click on “Create.”

Step 3: Implementing the Objective-C Calendar Module

In Xcode, create a new Objective-C file by right-clicking on the project directory, selecting “New File,” and choosing the “Objective-C File” template. Name the file “CalendarModule.”

In the “CalendarModule.h” file, import the necessary headers and declare the interface for the calendar module:

#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface CalendarModule : NSObject <RCTBridgeModule>
@end

In the “CalendarModule.m” file, implement the necessary methods to fetch and add calendar events:


#import <Foundation/Foundation.h>
#import "CalendarModule.h"
#import <EventKit/EventKit.h>

@implementation CalendarModule
RCT_EXPORT_MODULE()
RCT_EXPORT_METHOD(fetchEvents:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
EKEventStore *eventStore = [[EKEventStore alloc] init];
[eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
if (granted) {
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDate *startDate = [NSDate date];
NSDate *endDate = [calendar dateByAddingUnit:NSCalendarUnitYear value:1 toDate:startDate options:0];

NSPredicate *predicate = [eventStore predicateForEventsWithStartDate:startDate endDate:endDate calendars:nil];
NSArray<EKEvent *> *events = [eventStore eventsMatchingPredicate:predicate];

NSMutableArray<NSDictionary *> *formattedEvents = [NSMutableArray array];
for (EKEvent *event in events) {
[formattedEvents addObject:@{
@"title": event.title,
@"startDate": @(event.startDate.timeIntervalSince1970),
@"endDate": @(event.endDate.timeIntervalSince1970),
}];
}
resolve(formattedEvents);
} else {
reject(@"calendar_permission_error", @"Calendar permission denied", error);
}
}];
}

RCT_EXPORT_METHOD(addEvent:(NSString *)title startDate:(double)startDate endDate:(double)endDate resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
EKEventStore *eventStore = [[EKEventStore alloc] init];
[eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
if (granted) {
EKEvent *event = [EKEvent eventWithEventStore:eventStore];
event.title = title;
NSTimeInterval startDateInterval = startDate / 1000;
event.startDate = [NSDate dateWithTimeIntervalSince1970:startDateInterval];
NSTimeInterval endDateInterval = endDate / 1000;
NSLog(@"%@", [NSDate dateWithTimeIntervalSince1970:endDateInterval]);
event.endDate = [NSDate dateWithTimeIntervalSince1970:endDateInterval];
event.calendar = eventStore.defaultCalendarForNewEvents;

NSError *saveError = nil;
BOOL success = [eventStore saveEvent:event span:EKSpanThisEvent commit:YES error:&saveError];
if (success) {
resolve(@"Event added successfully");
} else {
reject(@"event_add_error", @"Error adding event", saveError);
}
} else {
reject(@"calendar_permission_error", @"Calendar permission denied", error);
}
}];
}
@end

Step 4: Registering the Module in the React Native Code

To communicate with the Objective-C module from our React Native code, we need to register the module. Open the “CalendarModule.ts” file in the “NativeModules” folder (or anywhere else you want) and add the following code:

import {NativeModules} from 'react-native';
import {Double} from 'react-native/Libraries/Types/CodegenTypes';

const {CalendarModule} = NativeModules;

export const fetchCalendarEvents = async () => {
try {
const events = await CalendarModule.fetchEvents();
return events;
} catch (error: any) {
throw new Error('Error fetching calendar events: ' + error.message);
}
};

export const addCalendarEvent = async (
title: string,
startDate: Double,
endDate: Double,
) => {
try {
const result = await CalendarModule.addEvent(title, startDate, endDate);
return result;
} catch (error: any) {
throw new Error('Error adding calendar event: ' + error.message);
}
};

Step 5: Updating the Info.plist

To use the EventKit framework, you need to add an NSCalendarsUsageDescription key to the Info.plist file of your project. This key provides a brief description of why the application requires access to the user’s calendar. Here’s how you can add NSCalendarsUsageDescription to the Info.plist:

  1. Open the Info.plist file of your React Native project from Xcode.
  2. Add a new row and set the key as “Privacy — Calendars Usage Description” (NSCalendarsUsageDescription).
  3. In the “Value” column, provide a brief description of why the application requires access to the calendar. For example: “To allow adding events to the calendar.”

Make sure to provide a clear and understandable description for users, as this message will be displayed when the app requests permission to access the calendar.

Step 6: Using the Calendar Integration in the App

In your App.tsx file, import and use the fetchCalendarEvents and addCalendarEvent functions to fetch the user's calendar events and add new events:

import React, {useEffect, useState} from 'react';
import {
View,
Text,
FlatList,
TouchableOpacity,
StyleSheet,
SafeAreaView,
} from 'react-native';
import {
fetchCalendarEvents,
addCalendarEvent,
} from './NativeModules/CalendarModule';

const App = () => {
const [calendarEvents, setCalendarEvents] = useState([]);

useEffect(() => {
async function fetchEvents() {
try {
const events = await fetchCalendarEvents();
setCalendarEvents(events);
} catch (error) {
console.log('Error fetching calendar events:', error);
}
}
fetchEvents();
}, []);

const handleAddEvent = async () => {
const title = 'New Event';
const startDate = (Date.now() / 1000 + 3600) * 1000;
const endDate = (Date.now() / 1000 + 3600 * 2) * 1000;
try {
await addCalendarEvent(title, startDate, endDate);
// Refresh the events list after adding a new event
const events = await fetchCalendarEvents();
setCalendarEvents(events);
} catch (error) {
console.log('Error adding calendar event:', error);
}
};

return (
<SafeAreaView style={styles.container}>
<Text style={styles.title}>My Calendar Events</Text>
<FlatList
data={calendarEvents}
renderItem={({item}) => (
<View style={styles.eventItem}>
<Text style={styles.eventTitle}>{item.title}</Text>
<Text style={styles.eventDate}>
{new Date(item.startDate * 1000).toDateString()}
</Text>
</View>
)}
keyExtractor={item => item.startDate.toString()}
/>
<TouchableOpacity style={styles.addButton} onPress={handleAddEvent}>
<Text style={styles.addButtonText}>Add Event</Text>
</TouchableOpacity>
</SafeAreaView>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
},
eventItem: {
marginBottom: 10,
},
eventTitle: {
fontSize: 18,
fontWeight: 'bold',
},
eventDate: {
fontSize: 14,
color: 'gray',
},
addButton: {
backgroundColor: '#007AFF',
borderRadius: 8,
padding: 12,
marginTop: 20,
},
addButtonText: {
color: '#FFFFFF',
fontSize: 16,
textAlign: 'center',
},
});

export default App;

Step 7: Running the App

To test the calendar integration, run the app on an iOS simulator or device using the following command:

npx react-native run-ios

Or run the app through the Xcode “Run” button in the desired simulator or in a real device

Before adding the event
Event added
iOS calendar

Conclusion:

Congratulations! You have successfully integrated the native iOS calendar into your React Native app using Objective-C. Users can now view their calendar events directly from the app and add new events, providing a seamless and efficient user experience. Feel free to explore additional functionalities or UI enhancements to further elevate your app’s capabilities. Happy coding!

Stay tuned for more exciting React Native tutorials to enhance your app development skills!

--

--

Giulio Milani

Mobile developer with 2+ years focused with React Native. Passionate about creating cross-platform apps with seamless performance and engaging user experiences.