I'm having a strange issue EKEventStore, iCloud and local calendars. If iCloud is enabled the Calendar is created and events are saved into the calendar as you would expect. if iCloud is turned off and you try to save an event nothing happens, however the device continues to create iCloud calendars in a loop every 3-5 seconds until iCloud is turned back on and then all of those calendars flood into iCloud as duplicates. I'm using nearly the exact code that has been referenced here on SO many times as well as in Apples Docs. I'm completely stumped as to why it's not working and there seems to be very little documentation on EKEventStore in general.
//••••••••••••••••••••••••••••••••••••••••••••••• #pragma mark – Save Event //•••••••••••••••••••••••••••••••••••••••••••••••
-(void)saveEventWithDate:(NSDate *)startDate endDate:(NSDate *)endDate
{
AppData *theData = [self theAppData];
if([self checkIsDeviceVersionHigherThanRequiredVersion:@"6.0"]) {
[eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) { // iOS 6 Support
if (granted){
NSLog(@"Access Granted");
} else {
NSLog(@"Access Not Granted");
}
}];
}
EKEvent *event = [EKEvent eventWithEventStore:eventStore];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([eventStore calendarWithIdentifier:[defaults objectForKey:@"My Calendar"]] != nil) // Calendar Existed
{
event.calendar = [eventStore calendarWithIdentifier:[defaults objectForKey:@"My Calendar"]];
NSLog(@"Calendar Existed");
} else { // Create Calendar
EKSource *theSource = nil;
for (EKSource* src in eventStore.sources) {
if ([src.title isEqualToString:@"iCloud"]) {
theSource = src;
break;
}
if (src.sourceType == EKSourceTypeLocal && theSource==nil) {
theSource = src;
break;
}
}
[self setupCalendarWithSource:theSource withEvent:event];
}
NSLog(@"Type of Event:%@",typeOfEvent);
if ([typeOfEvent isEqualToString:@"Hello"]) {
event.title = [NSString stringWithFormat:@"%@ Hello",[theData.hello_info objectForKey:@"customer_name"]];
event.location = [NSString stringWithFormat:@"Phone #%@",[theData.hello_info objectForKey:@"customer_phone_number"]];
event.notes = [NSString stringWithFormat:@"Hello Issue: %@",[theData.hello_info objectForKey:@"hello_issue"]];
NSLog(@"Hello");
}
event.startDate = startDate;
event.endDate = endDate;
event.allDay = NO;
EKAlarm *alarm = [EKAlarm alarmWithRelativeOffset:-1800]; // Half Hour Before
event.alarms = [NSArray arrayWithObject:alarm];
[eventStore saveEvent:event span:EKSpanThisEvent error:nil];
SAFE_PERFORM_WITH_ARG(_delegate, @selector(wasScheduled), nil);
}
-(void)setupCalendarWithSource:(EKSource *)theSource withEvent:(EKEvent *)event {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
EKCalendar *cal;
cal = [EKCalendar calendarWithEventStore:eventStore];
cal.title = @"My Appointments";
cal.source = theSource;
[eventStore saveCalendar:cal commit:YES error:nil];
NSLog(@"cal id = %@", cal.calendarIdentifier);
NSString *calendar_id = cal.calendarIdentifier;
[defaults setObject:calendar_id forKey:@"My Calendar"];
event.calendar = cal;
}
I'm not sure why you get this behavior, but I think due to the fact that having disabled iCloud, the system cannot make queries on it and then queue creation requests that are resolved once you wake iCloud (but I'm assuming).
Anyway, the first solution that comes to my mind is to check if iCloud is active or not in this way
EKSource *defaultSource = [eventStore defaultCalendarForNewEvents].source;
if (defaultSource.sourceType == EKSourceTypeCalDAV)
NSLog(@"iCloud Enable");
else
NSLog(@"iCloud Disable");
this is done you can save your events to the default source properly and then keep the 2 calendars (the local one and the cloud one) synchronized with each other ...
Reactivation of iCloud will still be prompted to add all local calendars.
see also the second answer here Accessing programmatically created calendar on iOS device (which is where I got the idea ;) )
I hope I was helpful.
EDIT: Maybe is not necessary to create a second calendar... Try to change the source of the calendar from EKSourceTypeCalDAV to EKSourceTypeLocal ... don't forget to save calendar with commit "YES"
EDIT2: Ok just tested ...
substitute this:
} else { // Create Calendar
EKSource *theSource = nil;
for (EKSource* src in eventStore.sources) {
if ([src.title isEqualToString:@"iCloud"]) {
theSource = src;
break;
}
if (src.sourceType == EKSourceTypeLocal && theSource==nil) {
theSource = src;
break;
}
}
[self setupCalendarWithSource:theSource withEvent:event];
}
with this ...
} else { // Create Calendar
EKSource *theSource = [eventStore defaultCalendarForNewEvents].source;
[self setupCalendarWithSource:theSource withEvent:event];
}
this way u will create the calendar in the right source (local if user deactivate iCloud and CalDAV otherwise)
then:
1) when user choose to deactivate iCloud should leave calendars on iphone (and not delete) so u have the cloud calendar in the local source
2) when user choose to activate iCloud will merge his local calendars with the cloud and there u go!!
i hope this help
If you want a bullet proof way to find iCloud Calendar and revert back to local Calendar if the iCloud is disabled, use the code below. I have included some comments which may help:
for (EKSource *source in eventStore.sources) { //Check for iCloud
if (source.sourceType == EKSourceTypeCalDAV && [source.title isEqualToString:@"iCloud"]) {
NSLog(@"Found iCloud Service."); //Found iCloud
if([source calendarsForEntityType:EKEntityTypeEvent].count>0){ //Check to see if Calendar is enabled on iCloud
NSLog(@"iCloud Calendar is Enabled."); //Calendar is Enabled
if([self saveEventCalendarWithSource:source]){
return YES;
}
}else{
NSLog(@"iCloud Calendar is Disabled."); //Calendar is Disabled
}
}
}
//If we are here it means that we did not find iCloud Source with iCloud Name. Now trying any CalDAV type to see if we can find it
for (EKSource *source in self.reminderStore.sources) { //Check for iCloud
if (source.sourceType == EKSourceTypeCalDAV) {
[self logData:@"Trying to save calendar in EKSourceTypeCalDAV Service."];
if([self saveEventCalendarWithSource:source]){
return YES;
}
}
}
//If we are here it means that we did not find iCloud and that means iCloud is not turned on. Use Local service now.
for (EKSource *source in self.reminderStore.sources) { //Look for Local Source
if (source.sourceType == EKSourceTypeLocal){ //Found Local Source
NSLog(@"Found Local Source.");
if([self saveEventCalendarWithSource:source]){
return YES;
}
}
}
Here's the code to save Calendar:
- (Boolean) saveEventCalendarWithSource:(EKSource *)source{
EKCalendar *Calendar = nil;
MyCalendar = [EKCalendar calendarForEntityType:EKEntityTypeEvent eventStore:eventStore];
MyCalendar.title = @"XXX";
MyCalendar.CGColor = [UIColor blueColor].CGColor;
MyCalendar.source = source;
NSError *err;
if([eventStore saveCalendar:MyCalendar commit:YES error:&err]){
if(MyCalendar.calendarIdentifier == nil){
NSLog(@"Could not save Calendar: %@",err);
return FALSE;
}
NSLog(@"Calendar Created. Here's the identifier %@",[MyCalendar calendarIdentifier]);
return TRUE;
}
NSLog(@"Could not create calendar! Reason:%@",err.description);
return FALSE;
}
Your post was a big help, i was struggling witt the exact same bug. Thank you !
I just made a small modifications, as the solution of using the defaultCalendarForNewEvents' source was not working in every situation : some source won't let you create new calendars in them.
I just check the number of calendars in my icloud source. If the count is zero, then the calendar sync is off, and I take the local source :
EKSource* localSource = nil;
EKSource* iCloudSource = nil;
for (EKSource* source in _eventStore.sources){
if (source.sourceType == EKSourceTypeLocal){
localSource = source;
}else if(source.sourceType == EKSourceTypeCalDAV && [source.title isEqualToString:@"iCloud"]){
iCloudSource = source;
}
}
if (iCloudSource && [iCloudSource.calendars count] != 0) {
calendar.source = iCloudSource;
}else{
calendar.source = localSource;
}
来源:https://stackoverflow.com/questions/13869118/ios-ekevent-store-recreating-icloud-calendars-in-a-loop-wont-save-local