3

I'm working on an app which needs to publish data to the user's calDAV calendar (iCloud or other). In order to do this, I need to detect if that source allows the creation of calendars and reminders.

EKSource doesn't offer much in terms of detecting anything, except provide you with the type of source (local, calDAV, Exchange...)

The only way I've thought of to detect if this is possible is to actually try to write a new Calendar and look at any error messages, like this:

 -(BOOL)ekSourceWritesToEvents:(EKSource *)ekSource {
    BOOL writesToEvents = NO;
    //Try to write a calendar to it if it fails, return NO
    //If you succeed, return YES, since this is uncommitted, no damage done
    NSError *error = nil;
    EKCalendar *testCalendar = [EKCalendar calendarForEntityType:EKEntityTypeEvent eventStore:self.eventStore];
    testCalendar.title = @"TestCalendar";
    testCalendar.source =  ekSource;

    BOOL result = [self.eventStore saveCalendar:testCalendar commit:NO error:&error];
    if (result) {
        NSLog(@"This Source can create calendars: %@",ekSource.title);

        //Now check if one can create an event
        EKEvent *newEvent = [EKEvent eventWithEventStore:self.eventStore];
        newEvent.title = @"TestEvent";
        newEvent.startDate = [NSDate date];
        newEvent.endDate = [NSDate date];
        newEvent.calendar = testCalendar;

        NSError *eventCreateError = nil;
        [self.eventStore saveEvent:newEvent span:EKSpanThisEvent commit:NO error:&eventCreateError];

        if (eventCreateError) {
            NSLog(@"Cannot Create EKEvent in test Calendar %@",eventCreateError.localizedDescription);
            writesToEvents = NO;
        } else {
            writesToEvents = YES;
            //Delete it even if uncommitted or it seems to get commited at some point
            NSError *eventDeleteError = nil;
            [self.eventStore removeEvent:newEvent span:EKSpanThisEvent commit:YES error:&eventDeleteError];
            if (eventDeleteError) {
                NSLog(@"Error removing event: %@",eventDeleteError.localizedDescription);
            }

        }
        NSError *calendarDeleteError = nil;
        [self.eventStore removeCalendar:testCalendar commit:YES error:&calendarDeleteError];
        if (calendarDeleteError) {
            NSLog(@"Error removing test calendar: %@",calendarDeleteError.localizedDescription);
        }

    } else {
        NSLog(@"Cannot Save Calendar: %@.", error);
    }
    return writesToEvents;
}

I find this a clunky way of doing things... would there not be a way to detect this properly? I would rather the user know what to expect before selecting a destination Source which is why I want to do this before actually trying.

Thanks!

UPDATE: It seems checking for Calendar creation is not enough. You must also then test that the created calendar allows Writing... I'm investigating. Why could one create a calendar and not allow it to write is beyond me...

Update 2: Seems that even if we don't commit, the test calendar gets created somehow. So I've added code to remove the test event and Calendar.

UPDATE 3: More problems... checking for Calendar creation capability is not all... some services (Google) allow you to create events in your calendar, but not Calendars. You must create the Calendar using the Google site, or their API. On top of this, there seems to be a bug with Exchange servers when you try to create an EKEvent, it actually gives you an error that the calendar cannot create reminders. Weird.

3 Answers3

1

To create an EKCalender, EKEvent, you need permission to create an EKSource(iCloud, Birthday, Google Account). if you print or dump EKSource object, you can see this data.

EKSource <0x3734900>, displayOrder:-1, isDelegate:0, isEnabledForEvents:1, isEnabledForReminders:1, ownerAddresses:{(
)}, supportsAttendeeComments:0, supportsDropBoxAttachments:0, supportsEmailValidation:0, supportsEventCalendarCreation:0, supportsFreebusy:0, supportsJunkReporting:0, supportsLikenessPropagation:0, supportsPhoneNumbers:0, supportsPrivateEvents:0, supportsManagedAttachments:0, supportsSharingScheduling:0, supportsTaskCalendarCreation:0, typeString:Birthday, supportsUnbind:1, isWritable:1, sourceIdentifier:Birthday
    title               : Other

I dont know why Apple docment did not describe about these properties ,, but if some EKSource(iCloud, Birthday, Google Account) allows creating or editting event calendar, "supportsEventCalendarCreation" set to 1. if it set to 0, fail to add EKCalendar and EKEvent. (Property which allowing create to reminder is maybe "supportsTaskCalendarCreation")

You need to use this method to access the property. https://developer.apple.com/documentation/objectivec/nsobject/1412591-valueforkey? language=objc

Swift code is below.

func canWritesEventsToEKSource(ekSource:EKSource) -> Bool {
    if let value = ekSource.value(forKey: "supportsEventCalendarCreation") as? Int,
        value == 1 {
        return true
    } else {
        return false
    }
}

func canWritesRemindersToEKSource(ekSource:EKSource) -> Bool {
    if let value = ekSource.value(forKey: "supportsTaskCalendarCreation") as? Int,
        value == 1 {
        return true
    } else {
        return false
    }
}
kj13
  • 1,765
  • 1
  • 11
  • 14
0

If you are using an Event Edit View Controller you can just use this to get calendar.

-(EKCalendar *)eventEditViewControllerDefaultCalendarForNewEvents:(EKEventEditViewController *)controller {

    EKCalendar *defaultCalendar = [[eventStore defaultCalendarForNewEvents] init];

    return defaultCalendar;
}
Tom Testicool
  • 563
  • 7
  • 26
  • Well, I wasn't using the Event Edit View controller, but mostly, the question was about Calendar creation in an account, not Event or Reminder creation which your answer seems to point to, but thanks. – Renaud Boisjoly Jan 28 '15 at 02:03
  • At least in iOS9, even the event source of the default Calendar does not guarantee it allows calendar creation. – leogdion Sep 17 '15 at 10:48
0

I stumbled across this while looking to solve the same problem. I was thinking of trying to test calendar creation as well so I'm with you on that part. Instead of then testing for event creation though, why not just check the new calendar to see if it allows modifying events with its allowsContentModifications property?

vichudson1
  • 881
  • 1
  • 10
  • 21
  • Yes, testing for event/reminder creation is fine this way, the question is about Calendar creation to start with. There is no way to test for calendar creation except trying to create one and get an error or not, and upon success, delete the test calendar or use it. In my case, I wanted to get the list of possible targets to place a Calendar, so I did not want to try and fail or allow the user to pick one which was not writeable, so I had to do it this way. – Renaud Boisjoly Jan 28 '15 at 02:02
  • Yeah totally. I got that. I was just meaning the test events weren't really necessary. It's really a shame there isn't a simple api call to solve this. – vichudson1 Jan 28 '15 at 02:04