Skip to content

Commit

Permalink
fix: ios17 permissions async request completes before permissions dia…
Browse files Browse the repository at this point in the history
…log choice has been selected, and full calendar access is always requested in ios17
  • Loading branch information
sjregan committed Oct 3, 2023
1 parent 239eba0 commit 1052bec
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 37 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ This plugin allows you to add events to the Calendar of the mobile device.
```
cordova plugin add cordova-plugin-calendar --variable CALENDAR_USAGE_DESCRIPTION="This app uses your calendar"
```
* On iOS 16 and below, all permission requests are identical. They all request full access.
* On iOS 17+, the `requestReadPermission()` and `requestReadWritePermission()` both request full access. `requestWritePermission()` only requests write permission.
* Note: calling requestWritePermission() after calling requestReadWritePermission() may mean you lose permission to read events. You may need to call requestReadWritePermission() again.
* On iOS 17+ you need to add `NSCalendarsFullAccessUsageDescription` and `NSCalendarsWriteOnlyAccessUsageDescription` usage strings to the `.plist` files.


### Android specifics
* Supported methods on Android 4: `find`, `create` (silent and interactive), `delete`, ..
Expand Down
3 changes: 2 additions & 1 deletion src/ios/Calendar.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
@property (nonatomic, retain) EKEventStore* eventStore;
@property (nonatomic, copy) NSString *interactiveCallbackId;

- (void)initEventStoreWithCalendarCapabilities;
- (void)initEventStoreWithFullCalendarCapabilities;
- (void)initEventStoreWithWriteCalendarCapabilities;

-(NSArray*)findEKEventsWithTitle: (NSString *)title
location: (NSString *)location
Expand Down
141 changes: 105 additions & 36 deletions src/ios/Calendar.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,38 +12,103 @@ @implementation Calendar
#pragma mark Initialization functions

- (void) pluginInitialize {
[self initEventStoreWithCalendarCapabilities];
}

- (void) initEventStoreWithCalendarCapabilities {
EKEventStore* eventStoreCandidate = [[EKEventStore alloc] init];

if (@available(iOS 17.0, *)) {
[eventStoreCandidate requestFullAccessToEventsWithCompletion:^(BOOL granted, NSError *error) {
if (granted) {
self.eventStore = eventStoreCandidate;
NSLog(@"Full access to the event store granted and eventStore initialized.");
} else {
NSLog(@"Access to the event store not granted. Error: %@", error.localizedDescription);
}
}];
} else {
__block BOOL accessGranted = NO;
EKEventStore* eventStoreCandidate = [[EKEventStore alloc] init];
if([eventStoreCandidate respondsToSelector:@selector(requestAccessToEntityType:completion:)]) {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[eventStoreCandidate requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
accessGranted = granted;
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
} else { // we're on iOS 5 or older
accessGranted = YES;
}
if (accessGranted) {
self.eventStore = eventStoreCandidate;
}
}
/**
* Init event store with read and write capabilities.
*
* Note: This will overwrite the existing event store.
*/
- (void) initEventStoreWithFullCalendarCapabilities {
NSLog(@"Initialising calendar event store with full access.");

EKEventStore* eventStoreCandidate = [[EKEventStore alloc] init];
__block BOOL accessGranted = NO;

dispatch_semaphore_t sema = dispatch_semaphore_create(0);
if (@available(iOS 17.0, *)) {
[eventStoreCandidate requestFullAccessToEventsWithCompletion:^(BOOL granted, NSError *error) {
accessGranted = granted;

if (!accessGranted) {
NSLog(@"Full access to the event store not granted. Error: %@", error.localizedDescription);
}

dispatch_semaphore_signal(sema);
}];
} else {
// iOS 16 or lower
if([eventStoreCandidate respondsToSelector:@selector(requestAccessToEntityType:completion:)]) {
[eventStoreCandidate requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
accessGranted = granted;

if (!accessGranted) {
NSLog(@"Full access to the event store not granted. Error: %@", error.localizedDescription);
}

dispatch_semaphore_signal(sema);
}];
} else {
// iOS 5 or lower
accessGranted = YES;
dispatch_semaphore_signal(sema);
}
}
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

if (accessGranted) {
self.eventStore = eventStoreCandidate;
NSLog(@"Full access to the event store granted and eventStore initialized.");
}
}

/**
* Init event store with write capabilities only.
*
* Note: This will overwrite the existing event store, so you may not be able to read events after requesting write
* only permission, even if you have previously requested read and write permission.
*/
- (void) initEventStoreWithWriteCalendarCapabilities {
NSLog(@"Initialising calendar event store with write access.");

EKEventStore* eventStoreCandidate = [[EKEventStore alloc] init];
__block BOOL accessGranted = NO;

dispatch_semaphore_t sema = dispatch_semaphore_create(0);
if (@available(iOS 17.0, *)) {
[eventStoreCandidate requestWriteOnlyAccessToEventsWithCompletion:^(BOOL granted, NSError *error) {
accessGranted = granted;

if (!accessGranted) {
NSLog(@"Write access to the event store not granted. Error: %@", error.localizedDescription);
}

dispatch_semaphore_signal(sema);
}];
} else {
// iOS 16 or lower
if([eventStoreCandidate respondsToSelector:@selector(requestAccessToEntityType:completion:)]) {
[eventStoreCandidate requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
accessGranted = granted;

if (!accessGranted) {
NSLog(@"Write access to the event store not granted after requesting full access for iOS <= 16. Error: %@", error.localizedDescription);
}

dispatch_semaphore_signal(sema);
}];
} else {
// iOS 5 or lower
accessGranted = YES;
dispatch_semaphore_signal(sema);
}
}
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

if (accessGranted) {
self.eventStore = eventStoreCandidate;
NSLog(@"Write access to the event store granted and eventStore initialized.");
}
}

#pragma mark Helper Functions
Expand Down Expand Up @@ -1055,7 +1120,7 @@ - (void)hasReadPermission:(CDVInvokedUrlCommand*)command {
}

- (void)requestReadPermission:(CDVInvokedUrlCommand*)command {
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:[self requestCalendarAccess]];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:[self requestFullCalendarAccess]];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

Expand All @@ -1065,7 +1130,7 @@ - (void)hasWritePermission:(CDVInvokedUrlCommand*)command {
}

- (void)requestWritePermission:(CDVInvokedUrlCommand*)command {
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:[self requestCalendarAccess]];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:[self requestWriteCalendarAccess]];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

Expand All @@ -1075,14 +1140,18 @@ - (void)hasReadWritePermission:(CDVInvokedUrlCommand*)command {
}

- (void)requestReadWritePermission:(CDVInvokedUrlCommand*)command {
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:[self requestCalendarAccess]];
CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:[self requestFullCalendarAccess]];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

-(CDVCommandStatus)requestCalendarAccess{
[self initEventStoreWithCalendarCapabilities];
return (self.eventStore != nil) ? CDVCommandStatus_OK : CDVCommandStatus_ERROR;
-(CDVCommandStatus)requestFullCalendarAccess{
[self initEventStoreWithFullCalendarCapabilities];
return (self.eventStore != nil) ? CDVCommandStatus_OK : CDVCommandStatus_ERROR;
}

-(CDVCommandStatus)requestWriteCalendarAccess{
[self initEventStoreWithWriteCalendarCapabilities];
return (self.eventStore != nil) ? CDVCommandStatus_OK : CDVCommandStatus_ERROR;
}

@end

0 comments on commit 1052bec

Please sign in to comment.