iOS EKEvent Store повторно создает календари iCloud в цикле, не сохраняет локально.

У меня странная проблема с EKEventStore, iCloud и локальными календарями. Если iCloud включен, календарь создается, и события сохраняются в календаре, как и следовало ожидать. если iCloud выключен, и вы пытаетесь сохранить событие, ничего не происходит, однако устройство продолжает создавать календари iCloud в цикле каждые 3–5 секунд, пока iCloud не будет снова включен, а затем все эти календари передаются в iCloud как дубликаты. Я использую почти точный код, на который много раз ссылались здесь на SO, а также в Apples Docs. Я совершенно озадачен тем, почему он не работает, и в целом по EKEventStore очень мало документации.

//••••••••••••••••••••••••••••••••••••••••••••••••• # знак прагмы – Сохранить событие //•••••••••••••••••••••••••••••••••••••••••••• ••••

-(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;
}

person KDM    schedule 13.12.2012    source источник


Ответы (3)


Я не уверен, почему у вас такое поведение, но я думаю, что из-за того, что отключив iCloud, система не может делать запросы к нему, а затем ставить в очередь запросы на создание, которые разрешаются после пробуждения iCloud (но я предполагаю).

Во всяком случае, первое решение, которое приходит мне в голову, это проверить, активен ли iCloud таким образом.

EKSource *defaultSource = [eventStore defaultCalendarForNewEvents].source;

if (defaultSource.sourceType == EKSourceTypeCalDAV)
    NSLog(@"iCloud Enable");
else
    NSLog(@"iCloud Disable");

это сделано, вы можете правильно сохранить свои события в источнике по умолчанию, а затем синхронизировать 2 календаря (локальный и облачный) друг с другом ...

При повторной активации iCloud все равно будет предложено добавить все локальные календари.

см. также второй ответ здесь Доступ к программно созданному календарю на устройстве iOS ( откуда я взял идею ;))

Надеюсь, я был полезен.

РЕДАКТИРОВАТЬ: Возможно, нет необходимости создавать второй календарь... Попробуйте изменить источник календаря с EKSourceTypeCalDAV на EKSourceTypeLocal... не забудьте сохранить календарь с фиксацией "YES"

EDIT2: Хорошо, только что протестировано...

замените это:

} 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];
}

с этим ...

} else { // Create Calendar

    EKSource *theSource = [eventStore defaultCalendarForNewEvents].source;

    [self setupCalendarWithSource:theSource withEvent:event];
}

таким образом вы создадите календарь в правильном источнике (локальном, если пользователь деактивирует iCloud и CalDAV в противном случае)

тогда:

1) когда пользователь решит деактивировать iCloud, он должен оставить календари на iphone (а не удалять), чтобы у вас был облачный календарь в локальном источнике

2) когда пользователь решит активировать iCloud, он объединит свои локальные календари с облаком, и все!

я надеюсь, что это поможет

person Alex75    schedule 21.12.2012
comment
Спасибо, не знаю, почему так много разработчиков использовали этот другой код (все учебники, которые я нашел, были одинаковыми), но изменение этого заставляет его работать так, как ожидалось. - person KDM; 25.12.2012
comment
рад узнать, что вы решили :). К сожалению, по моему опыту, туториалы всегда базовые и редко обновляются.... Может быть, они были написаны до iCloud... В любом случае нельзя сказать, что не работает, просто... не полное... вроде любой уважающий себя учебник :p - person Alex75; 04.01.2013
comment
Вы не можете просто использовать [eventStore defaultCalendarForNewEvents].source для создания календаря. Учетные записи Gmail также используют EKSourceTypeCalDAV, и они не позволяют создавать календари. Вот почему все используют метод поиска, а не источник по умолчанию. Если у вас есть учетная запись Gmail, опубликованный код может вернуть iCloud Enable, даже если вы отключите iCloud. - person Bms270; 20.02.2014
comment
Ответ длиннее, чем я могу поместить в область комментариев, и исходный вопрос не имеет отношения к этому коду. Вы можете опубликовать новый вопрос и сообщить мне, чтобы я мог опубликовать свой ответ. - person Bms270; 04.03.2014
comment
@ Bms270 Хорошо, код, решающий этот вопрос, находится в разделе редактирования 2 моего сообщения, и объяснение содержит поведение, которое должен иметь конечный пользователь (и обычно это так). Я спросил, есть ли у вас лучший код, который решает этот вопрос, так как это был также МОЙ вопрос до того, как KDM опубликовал его;) если да, вы можете опубликовать его в новом ответе, я думаю. - person Alex75; 06.03.2014

Если вам нужен пуленепробиваемый способ найти календарь iCloud и вернуться к локальному календарю, если iCloud отключен, используйте приведенный ниже код. Я включил некоторые комментарии, которые могут помочь:

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;
        }

    }
}

Вот код для сохранения календаря:

- (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;

}

person Bms270    schedule 03.03.2014
comment
Этот код по существу найдет либо iCloud, если он включен, либо локальный, если iCloud не включен. Это будет работать, даже если пользователь изменит поле «Описание iCloud» на другое. - person Bms270; 10.03.2014

Ваш пост очень помог, я боролся с точно такой же ошибкой. Спасибо !

Я просто внес небольшие изменения, так как решение с использованием источника defaultCalendarForNewEvents работало не во всех ситуациях: некоторые источники не позволяли создавать в них новые календари.

Я просто проверяю количество календарей в своем источнике icloud. Если счет равен нулю, то синхронизация календаря отключена, и я беру локальный источник:

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;
}
person Emilie    schedule 22.03.2013