iOS 日历提醒 EKEvent EKReminder

一、简介

EventKit 允许你的app 访问已经存在于日历和提醒里面的事件,同时也允许你的app创建一个提醒事件添加到日历或者提醒中。用户可以编辑删除日历和提醒里面的事件。EventKit 还可以添加闹铃,和重复提醒的事件。

EKEvent创建的事件在日历中显示,EKReminder创建的时间在提醒事项中显示

注意:EKEvent 和 EKReminder都继承于EKCalendarItem, EKCalendarItem类是日历事件和提醒的抽象超类,此类提供常用的属性和方法来访问日历项的属性,例如设置日历的功能,标题和位置,以及支持附加注释,显示与会者,设置多个闹钟和指定重复规则。EKCalendarItem 的calendarItemIdentifier 是它唯一标示,使用EKCalendarItem *item = [store calendarItemWithIdentifier:identifer];可以通过一个唯一标示获取一个EKCalendarItem对象

二、配置权限

iOS 10 之后如果app 想要访问日历和提醒事项,我们必须在 我们的app 的Info.plist文件中添加NSRemindersUsageDescription和NSCalendarsUsageDescription的key否则app会闪退

  // 获取日历权限
 EKAuthorizationStatus ekStatus = [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent]

  // 申请日历权限
 [self.eventStore] requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError * _Nullable error) {

 }];
  // 获取提醒事项权限
 EKAuthorizationStatus ekStatus = [EKEventStore authorizationStatusForEntityType: EKEntityTypeReminder]

  // 申请提醒事项权限
 [self.eventStore] requestAccessToEntityType:EKEntityTypeReminder completion:^(BOOL granted, NSError * _Nullable error) {

 }];

三、创建自定义日历

/**
 *  创建日历
 *
 *  @param entityType      日历类型 EKEntityTypeEvent EKEntityTypeReminder
 *  @param title      日历标题
 *  @param completion 回调方法
 */
- (void)createCustomCalendarWithEntityType:(EKEntityType)entityType title:(NSString *)title completion:(CalendarCompletion)completion {
    // 创建日历
    EKCalendar * customCalendar = [EKCalendar calendarForEntityType:entityType eventStore:self.eventStore];
    //  必须设置source,否则无法创建calendar
    customCalendar.source = [self getAvailableCalendarSource];
    customCalendar.title = title;
    
    NSError *error = nil;
    BOOL isSuccess = [self.eventStore saveCalendar:customCalendar commit:YES error:&error];
    
    if (!error) {
        if (![NSString isBlankString:customCalendar.calendarIdentifier]) {
            
            //存储calendarIdentifier
            NSString *calendarIdfLocalKey = nil;
            if (entityType == EKEntityTypeEvent) {
                calendarIdfLocalKey = kEventCalendarIdentifierLocalKey;
            }else{
                calendarIdfLocalKey = kReminderCalendarIdentifierLocalKey;
            }
            [XTUserDefault setValue:customCalendar.calendarIdentifier forKey:calendarIdfLocalKey];
            
        }else{
            isSuccess = NO;
            error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSLocalizedDescriptionKey : @"eventIdentifier不存在"}];
        }
    }else{
        isSuccess = NO;
    }
    
   !completion?:completion(isSuccess,error);
    
}

- (EKSource *)getAvailableCalendarSource {
    EKSource *localSource = nil;
    for (EKSource *source in self.eventStore.sources){
        if (source.sourceType == EKSourceTypeCalDAV && [source.title isEqualToString:@"iCloud"]){
            localSource = source;
            break;
        }
    }
    if (localSource == nil){
        for (EKSource *source in self.eventStore.sources){
            if (source.sourceType == EKSourceTypeLocal){
                localSource = source;
                break;
            }
        }
    }
    
    if (localSource == nil) {
        NSLog(@"XTCalendarEventManager getAvailableCalendarSource error");
    }
    
    return localSource;
}

注意1:创建的EKCalendar必须指定source,否则saveCalendar会失败

注意2: 不是所有的source都可用于添加提醒事件的,一般来说,系统开启iCloud,使用iCloud的Source,如果系统关闭iCloud,使用本地Source

注意3:因为 EventStore 是 Calendar database 的数据库引擎,所以应该尽量少的对他进行创建和销毁,所以推荐使用EventStore的时候使用单例模式

注意4: 需要本地保存创建的Calendar的identifier。

四、创建日历提醒事件EKEvent

/**
 *  添加日历提醒事项
 *
 *  @param eventIdentifierLocalKey  事件ID 本地存储Key
 *  @param title      事件标题
 *  @param startDate  开始时间
 *  @param endDate    结束时间
 *  @param allDay     是否全天
 *  @param alarmArray 闹钟集合(传nil,则没有)
 *  @param notes      事件备注(传nil,则没有)
 *  @param url        事件url(传nil,则没有)
 *  @param completion 回调方法
 */
- (void)createCalendarEventWithEventIdentifierLocalKey:(NSString *)eventIdentifierLocalKey calendarTitle:(NSString *)title notes:(NSString * __nullable)notes startDate:(NSDate *)startDate endDate:(NSDate *)endDate allDay:(BOOL)allDay alarmArray:(NSArray <NSString *> * __nullable)alarmArray url:(NSURL * __nullable)url completion:(CalendarCompletion)completion {
    
    if (![self checkCaledarAuthority:EKEntityTypeEvent]) {
        NSError * authorityError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSLocalizedDescriptionKey : @"未授权"}];
        !completion?:completion(NO,authorityError);
        return;
    }
    
    
    BOOL isSuccess = NO;
    EKEvent *event = [EKEvent eventWithEventStore:self.eventStore];
    event.title = title;
    event.allDay = allDay;
    event.notes = notes;
    event.URL = url?:[NSURL URLWithString:@"xtcloudmeeting://"];
    event.startDate = startDate;
    event.endDate = endDate;
    
    //添加闹钟提醒
    if (alarmArray && alarmArray.count > 0) {
        for (NSString *timeString in alarmArray) {
            [event addAlarm:[EKAlarm alarmWithRelativeOffset:[timeString integerValue]]];
        }
    }
    
    // 存储到日历源中
    EKCalendar *eventCalendar = [self findCustomCalendar:EKEntityTypeEvent];
    if (eventCalendar) {
        [event setCalendar:eventCalendar];
    }else{
        [event setCalendar:[self.eventStore defaultCalendarForNewEvents]];
        NSLog(@"XTCalendarEventManager save defaultCalendarForNewEvents");
    }
    
    // 保存日历
    NSError *isError;
    isSuccess = [self.eventStore saveEvent:event span:EKSpanThisEvent commit:YES error:&isError];
    
    if (!isError) {
        if (![NSString isBlankString:event.eventIdentifier] && ![NSString isBlankString:eventIdentifierLocalKey]) {
            //存储日历ID
            [XTUserDefault setValue:event.eventIdentifier forKey:[self getCalendarItemLocalKey:eventIdentifierLocalKey type:EKEntityTypeEvent]];
        }else{
            isSuccess = NO;
            isError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSLocalizedDescriptionKey : @"eventIdentifier不存在"}];
        }
    }
    
    !completion?:completion(isSuccess,isError);
    
    NSLog(@"XTCalendarEventManager createCalendarEventWithEventIdentifierLocalKey %@",eventIdentifierLocalKey);
    
}

注意:也需要保存创建的EKEvent的identifier

五、查询EKEvent

/**
 *  查日历事件
 *
 *  @param eventIdentifierLocalKey    事件ID(标识符) 本地存储Key
 */
- (EKEvent *)checkCalendarEventWithIdentifier:(NSString *)eventIdentifierLocalKey{
    
    if (![self checkCaledarAuthority:EKEntityTypeEvent]) {
        NSLog(@"XTCalendarEventManager checkCalendarEventWithIdentifier 未授权");
        return nil;
    }
    
    NSString *eIdentifier = [XTUserDefault getValueForKey:[self getCalendarItemLocalKey:eventIdentifierLocalKey type:EKEntityTypeEvent]];
    if (![NSString isBlankString:eIdentifier]) {
        EKEvent *event = [self.eventStore eventWithIdentifier:eIdentifier];
        return event;
    }
    return nil;
}

/**
 *  查日历事件(可查询一段时间内的事件)
 *
 *  @param startDate  开始时间
 *  @param endDate    结束时间
 *  @param modifytitle    标题,为空则都要查询
 */
- (NSArray *)checkCalendarEventWithStartDate:(NSDate *)startDate endDate:(NSDate *)endDate modifytitle:(NSString * __nullable)modifytitle {
    
    if (![self checkCaledarAuthority:EKEntityTypeEvent]) {
        NSLog(@"XTCalendarEventManager checkCalendarEventWithDate 未授权");
        return nil;
    }
    
    // 查询到所有的日历
    EKCalendar *eventCalendar = [self findCustomCalendar:EKEntityTypeEvent];
    
    if (!eventCalendar) {
        eventCalendar = [self.eventStore defaultCalendarForNewEvents];
    }
    
    NSPredicate *predicate = [self.eventStore predicateForEventsWithStartDate:startDate endDate:endDate calendars:@[eventCalendar]];
    
    // 获取到范围内的所有事件
    NSArray *request = [self.eventStore eventsMatchingPredicate:predicate];
    // 按开始事件进行排序
    request = [request sortedArrayUsingSelector:@selector(compareStartDateWithEvent:)];
    
    if ([NSString isBlankString:modifytitle]) {
        return request;
    }else{
        NSMutableArray *onlyRequest = [NSMutableArray array];
        for (int i = 0; i < request.count; i++) {
            EKEvent *event = request[i];
            if (event.title && [event.title isEqualToString:modifytitle]) {
                [onlyRequest addObject:event];
            }
        }
        return onlyRequest;
    }
}

六、删除EKEvent


/**
 *  删除日历事件(删除单个)
 *
 *  @param eventIdentifierLocalKey    事件ID(标识符) 本地存储Key
 */
- (BOOL)deleteCalendarEventWithIdentifier:(NSString *)eventIdentifierLocalKey{
    
    if (![self checkCaledarAuthority:EKEntityTypeEvent]) {
        NSLog(@"XTCalendarEventManager deleteCalendarEventWithIdentifier 未授权");
        return NO;
    }
    
    BOOL isDelete = NO;
    
    NSString *eIdentifier = [XTUserDefault getValueForKey:[self getCalendarItemLocalKey:eventIdentifierLocalKey type:EKEntityTypeEvent]];
    EKEvent *event;
    if (![NSString isBlankString:eIdentifier]) {
        event = [self.eventStore eventWithIdentifier:eIdentifier];
        isDelete = [self.eventStore removeEvent:event span:EKSpanThisEvent error:nil];
    }
    NSLog(@"XTCalendarEventManager deleteCalendarEventWithIdentifier %@ %d",eventIdentifierLocalKey,isDelete);
    return isDelete;
}

/**
 *  删除日历事件(可删除一段时间内的事件)
 *
 *  @param startDate  开始时间
 *  @param endDate    结束时间
 *  @param modifytitle    标题,为空则都要删除
 */
- (BOOL)deleteCalendarEventWithStartDate:(NSDate *)startDate endDate:(NSDate *)endDate modifytitle:(NSString * __nullable)modifytitle {
    
    if (![self checkCaledarAuthority:EKEntityTypeEvent]) {
        NSLog(@"XTCalendarEventManager deleteCalendarEventWithDate 未授权");
        return NO;
    }
    
    // 获取到此事件
    NSArray *request = [self checkCalendarEventWithStartDate:startDate endDate:endDate modifytitle:modifytitle];
    
    for (int i = 0; i < request.count; i ++) {
        // 删除这一条事件
        EKEvent *event = request[i];
        NSError*error =nil;
        // commit:NO:最后再一次性提交
        [self.eventStore removeEvent:event span:EKSpanThisEvent commit:NO error:&error];
    }
    //一次提交所有操作到事件库
    NSError *errored = nil;
    BOOL commitSuccess= [self.eventStore commit:&errored];
    return commitSuccess;
}

七、修改EKEvent

/**
 *  修改日历事件
 *
 *  @param eventIdentifierLocalKey 事件ID(标识符) 本地key
 *  @param title      修改事件标题
 *  @param startDate  修改开始时间
 *  @param endDate    修改结束时间
 *  @param allDay     修改是否全天
 *  @param alarmArray 修改闹钟集合
 *  @param notes      修改事件备注
 *  @param url        修改事件url
 *  @param completion 回调方法
 */
- (void)modifyCalendarEventWithEventIdentifierLocalKey:(NSString *)eventIdentifierLocalKey calendarTitle:(NSString *)title notes:(NSString * __nullable)notes startDate:(NSDate *)startDate endDate:(NSDate *)endDate allDay:(BOOL)allDay alarmArray:(NSArray <NSString *> * __nullable)alarmArray url:(NSURL * __nullable)url completion:(CalendarCompletion)completion{
    
    if (![self checkCaledarAuthority:EKEntityTypeEvent]) {
        NSError * authorityError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSLocalizedDescriptionKey : @"未授权"}];
        !completion?:completion(NO,authorityError);
        return;
    }
    
    // 获取到此事件
    EKEvent *event = [self checkCalendarEventWithIdentifier:eventIdentifierLocalKey];
    if (event) {
        BOOL isDelete = [self deleteCalendarEventWithIdentifier:eventIdentifierLocalKey];
        NSLog(@"XTCalendarEventManager modify delete %d",isDelete);
        [self createCalendarEventWithEventIdentifierLocalKey:eventIdentifierLocalKey calendarTitle:title notes:notes startDate:startDate endDate:endDate allDay:allDay alarmArray:alarmArray url:url completion:completion];
    }else{
        // 没有此条日历
        [self createCalendarEventWithEventIdentifierLocalKey:eventIdentifierLocalKey calendarTitle:title notes:notes startDate:startDate endDate:endDate allDay:allDay alarmArray:alarmArray url:url completion:completion];
        
    }
}

八、创建EKReminder提醒事项

和EKEvent类似

/**
 *  添加Reminder提醒事项
 *
 *  @param reminderIdentifierLocalKey  事件ID 本地存储Key
 *  @param title      事件标题
 *  @param startDate  开始时间
 *  @param dueDate    预计结束时间
 *  @param alarmDate 闹钟Date
 *  @param notes      事件备注(传nil,则没有)
 *  @param url        事件url(传nil,则没有)
 *  @param completion 回调方法
 */
- (void)createCalendarReminderWithReminderIdentifierLocalKey:(NSString *)reminderIdentifierLocalKey calendarTitle:(NSString *)title notes:(NSString * __nullable)notes startDate:(NSDate *)startDate dueDate:(NSDate *)dueDate alarmDate:(NSDate *)alarmDate url:(NSURL * __nullable)url completion:(CalendarCompletion)completion {
    
    if (![self checkCaledarAuthority:EKEntityTypeReminder]) {
        NSError * authorityError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSLocalizedDescriptionKey : @"未授权"}];
        !completion?:completion(NO,authorityError);
        return;
    }
    
    EKReminder *reminder = [EKReminder reminderWithEventStore:self.eventStore];
    reminder.title = title;
    reminder.notes = notes;
    reminder.URL = url?:[NSURL URLWithString:@"xtcloudmeeting://"];
    
    reminder.startDateComponents = [[NSCalendar currentCalendar] components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitHour|NSCalendarUnitMinute|NSCalendarUnitSecond fromDate:startDate];
    reminder.dueDateComponents = [[NSCalendar currentCalendar] components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitHour|NSCalendarUnitMinute|NSCalendarUnitSecond fromDate:dueDate];
    
    //添加闹钟提醒
    [reminder addAlarm:[EKAlarm alarmWithAbsoluteDate:alarmDate]];
    
    // 存储到日历源中
    EKCalendar *reminderCalendar = [self findCustomCalendar:EKEntityTypeReminder];
    if (reminderCalendar) {
        [reminder setCalendar:reminderCalendar];
    }else{
        [reminder setCalendar:self.eventStore.defaultCalendarForNewReminders];
    }
    
    NSError *error = nil;
    BOOL isSuccess = [self.eventStore saveReminder:reminder commit:YES error:&error];
    
    if (!error) {
        if (![NSString isBlankString:reminder.calendarItemIdentifier] && ![NSString isBlankString:reminderIdentifierLocalKey]) {
            [XTUserDefault setValue:reminder.calendarItemIdentifier forKey:[self getCalendarItemLocalKey:reminderIdentifierLocalKey type:EKEntityTypeReminder]];
        }else{
            isSuccess = NO;
            error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSLocalizedDescriptionKey : @"eventIdentifier不存在"}];
        }
        
    }else{
        isSuccess = NO;
    }
    
    !completion?:completion(isSuccess,error);
    
    NSLog(@"XTCalendarEventManager createCalendarReminderWithReminderIdentifierLocalKey %@",reminderIdentifierLocalKey);
    
}

注意:

  1. EKReminder 可添加多个闹钟Alarm,但实际测试中发现,只有第一个会生效
  2. Alarm 通过alarmWithRelativeOffset 创建时,后面参数offset 表示闹钟出发时间和参照时间点的偏移量,为负数时,表示在参照时间点前offset秒触发。此时如果把Alarm添加到EKEvent上,参照时间点为startDate,如果是添加到EKReminder上,参照时间点为dueDate。建议给EKReminder添加闹钟Alarm时,使用alarmWithAbsoluteDate方式创建Alarm

九、查询EKReminder

/**
 *  查Reminder提醒项
 *
 *  @param reminderIdentifierLocalKey    事件ID(标识符) 本地存储Key
 */
- (EKReminder *)checkCalendarReminderWithIdentifier:(NSString *)reminderIdentifierLocalKey{
    
    if (![self checkCaledarAuthority:EKEntityTypeReminder]) {
        NSLog(@"XTCalendarEventManager checkCalendarReminderWithIdentifier 未授权");
        return nil;
    }
    NSString *eIdentifier = [XTUserDefault getValueForKey:[self getCalendarItemLocalKey:reminderIdentifierLocalKey type:EKEntityTypeReminder]];
    if (![NSString isBlankString:eIdentifier]) {
        EKCalendarItem *calendarItem = [self.eventStore calendarItemWithIdentifier:eIdentifier];
        if ([calendarItem isKindOfClass:[EKReminder class]]) {
            return (EKReminder *)calendarItem;
        }
        return nil;
    }
    return nil;
}

/**
 *  查询Reminder提醒事项 (一段时间)(以dueDate为参照)
 *
 *  @param startDate  开始时间
 *  @param endDate    结束时间
 *  @param completion 回调方法
 */
- (void)checkCalendarIncompleteRemindersWithDueDateStarting:(nullable NSDate *)startDate endDate:(nullable NSDate *)endDate completion:(CalendarFetchDataCompletion)completion{
    
    if (![self checkCaledarAuthority:EKEntityTypeReminder]) {
        !completion?:completion(nil);
        return;
    }
    
    EKCalendar *reminderCalendar = [self findCustomCalendar:EKEntityTypeReminder];
    
    if (!reminderCalendar) {
        reminderCalendar = [self.eventStore defaultCalendarForNewReminders];
    }
    
    NSPredicate *predicate = [self.eventStore predicateForIncompleteRemindersWithDueDateStarting:startDate ending:endDate calendars:@[reminderCalendar]];
    
    [self.eventStore fetchRemindersMatchingPredicate:predicate completion:^(NSArray<EKReminder *> * _Nullable reminders) {
        !completion?:completion(reminders);
    }];
}

十、删除EKReminder

/**
 *  删除Reminber(删除单个)
 *
 *  @param reminderIdentifierLocalKey    reminder ID(标识符) 本地存储Key
 */
- (BOOL)deleteCalendarReminderWithIdentifier:(NSString *)reminderIdentifierLocalKey {
    
    if (![self checkCaledarAuthority:EKEntityTypeReminder]) {
        NSLog(@"XTCalendarEventManager deleteCalendarReminderWithIdentifier 未授权");
        return NO;
    }
    
    BOOL isDelete = NO;
    EKReminder *reminder = [self checkCalendarReminderWithIdentifier:reminderIdentifierLocalKey];
    if (reminder) {
        isDelete = [self.eventStore removeReminder:reminder commit:YES error:nil];
    }
    NSLog(@"XTCalendarEventManager deleteCalendarReminderWithIdentifier %@ %d",reminderIdentifierLocalKey,isDelete);
    return isDelete;
}

/**
 *  删除Reminder提醒事项 (一段时间)(以dueDate为参照)
 *
 *  @param startDate  开始时间
 *  @param endDate    结束时间
 *  @param completion 回调方法
 */

- (void)deleteCalendarIncompleteRemindersWithDueDateStarting:(nullable NSDate *)startDate endDate:(nullable NSDate *)endDate completion:(CalendarCompletion)completion{
    
    if (![self checkCaledarAuthority:EKEntityTypeReminder]) {
        NSError * authorityError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSLocalizedDescriptionKey : @"未授权"}];
        !completion?:completion(NO,authorityError);
        return;
    }
    
    weakify_self
    [self checkCalendarIncompleteRemindersWithDueDateStarting:startDate endDate:endDate completion:^(NSArray *itemArray) {
        strongify_self
        for (int i=0; i<itemArray.count; i++) {
            EKReminder *tempReminder = (EKReminder *)[itemArray objectAtIndex:i];
            [self.eventStore removeReminder:tempReminder commit:NO error:nil];
        }
        NSError *error = nil;
        BOOL commitSucces = [self.eventStore commit:&error];
        !completion?:completion(commitSucces,error);
    }];
}

十一、 修改EKReminder


/**
 *  修改Reminder提醒事项
 *
 *  @param reminderIdentifierLocalKey  事件ID 本地存储Key
 *  @param title      事件标题
 *  @param startDate  开始时间
 *  @param dueDate    预计结束时间
 *  @param alarmDate 闹钟Date
 *  @param notes      事件备注(传nil,则没有)
 *  @param url        事件url(传nil,则没有)
 *  @param completion 回调方法
 */
- (void)modifyCalendarReminderWithReminderIdentifierLocalKey:(NSString *)reminderIdentifierLocalKey calendarTitle:(NSString *)title notes:(NSString * __nullable)notes startDate:(NSDate *)startDate dueDate:(NSDate *)dueDate alarmDate:(NSDate *)alarmDate url:(NSURL * __nullable)url completion:(CalendarCompletion)completion {
    
    if (![self checkCaledarAuthority:EKEntityTypeReminder]) {
        NSError * authorityError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSLocalizedDescriptionKey : @"未授权"}];
        !completion?:completion(NO,authorityError);
        return;
    }
    
    EKReminder *tempReminder = [self checkCalendarReminderWithIdentifier:reminderIdentifierLocalKey];
    if (tempReminder) {
        [self deleteCalendarReminderWithIdentifier:reminderIdentifierLocalKey];
        [self createCalendarReminderWithReminderIdentifierLocalKey:reminderIdentifierLocalKey calendarTitle:title notes:notes startDate:startDate dueDate:dueDate alarmDate:alarmDate url:url completion:completion];
    }else{
        [self createCalendarReminderWithReminderIdentifierLocalKey:reminderIdentifierLocalKey calendarTitle:title notes:notes startDate:startDate dueDate:dueDate alarmDate:alarmDate url:url completion:completion];
    }
    
}

完整代码

XTCalendarEventManager.h

#import <Foundation/Foundation.h>

#import <EventKit/EventKit.h>

typedef void(^__nullable CalendarCompletion)(BOOL isSuccess, NSError * _Nullable error);
typedef void(^__nullable CalendarFetchDataCompletion)(NSArray * _Nullable itemArray);

NS_ASSUME_NONNULL_BEGIN

@interface XTCalendarEventManager : NSObject
AS_SINGLETON(XTCalendarEventManager);


#pragma mark - 日历提醒 -

/**
 *  添加日历提醒事项
 *
 *  @param eventIdentifierLocalKey  事件ID 本地存储Key
 *  @param title      事件标题
 *  @param startDate  开始时间
 *  @param endDate    结束时间
 *  @param allDay     是否全天
 *  @param alarmArray 闹钟集合(传nil,则没有)
 *  @param notes      事件备注(传nil,则没有)
 *  @param url        事件url(传nil,则没有)
 *  @param completion 回调方法
 */
- (void)createCalendarEventWithEventIdentifierLocalKey:(NSString *)eventIdentifierLocalKey calendarTitle:(NSString *)title notes:(NSString * __nullable)notes startDate:(NSDate *)startDate endDate:(NSDate *)endDate allDay:(BOOL)allDay alarmArray:(NSArray <NSString *> * __nullable)alarmArray url:(NSURL * __nullable)url completion:(CalendarCompletion)completion;


/**
 *  查日历事件
 *
 *  @param eventIdentifierLocalKey    事件ID(标识符) 本地存储Key
 */
- (EKEvent *)checkCalendarEventWithIdentifier:(NSString *)eventIdentifierLocalKey;


/**
 *  查日历事件(可查询一段时间内的事件)
 *
 *  @param startDate  开始时间
 *  @param endDate    结束时间
 *  @param modifytitle    标题,为空则都要查询
 */
- (NSArray *)checkCalendarEventWithStartDate:(NSDate *)startDate endDate:(NSDate *)endDate modifytitle:(NSString * __nullable)modifytitle;


/**
 *  删除日历事件(删除单个)
 *
 *  @param eventIdentifierLocalKey    事件ID(标识符) 本地存储Key
 */
- (BOOL)deleteCalendarEventWithIdentifier:(NSString *)eventIdentifierLocalKey;


/**
 *  删除日历事件(可删除一段时间内的事件)
 *
 *  @param startDate  开始时间
 *  @param endDate    结束时间
 *  @param modifytitle    标题,为空则都要删除
 */
- (BOOL)deleteCalendarEventWithStartDate:(NSDate *)startDate endDate:(NSDate *)endDate modifytitle:(NSString * __nullable)modifytitle;

/**
 *  修改日历事件
 *
 *  @param eventIdentifierLocalKey 事件ID(标识符) 本地key
 *  @param title      修改事件标题
 *  @param startDate  修改开始时间
 *  @param endDate    修改结束时间
 *  @param allDay     修改是否全天
 *  @param alarmArray 修改闹钟集合
 *  @param notes      修改事件备注
 *  @param url        修改事件url
 *  @param completion 回调方法
 */
- (void)modifyCalendarEventWithEventIdentifierLocalKey:(NSString *)eventIdentifierLocalKey calendarTitle:(NSString *)title notes:(NSString * __nullable)notes startDate:(NSDate *)startDate endDate:(NSDate *)endDate allDay:(BOOL)allDay alarmArray:(NSArray <NSString *> * __nullable)alarmArray url:(NSURL * __nullable)url completion:(CalendarCompletion)completion;




#pragma mark - Reminder 提醒 -
/**
 *  添加Reminder提醒事项
 *
 *  @param reminderIdentifierLocalKey  事件ID 本地存储Key
 *  @param title      事件标题
 *  @param startDate  开始时间
 *  @param dueDate    预计结束时间
 *  @param alarmDate 闹钟Date
 *  @param notes      事件备注(传nil,则没有)
 *  @param url        事件url(传nil,则没有)
 *  @param completion 回调方法
 */
- (void)createCalendarReminderWithReminderIdentifierLocalKey:(NSString *)reminderIdentifierLocalKey calendarTitle:(NSString *)title notes:(NSString * __nullable)notes startDate:(NSDate *)startDate dueDate:(NSDate *)dueDate alarmDate:(NSDate *)alarmDate url:(NSURL * __nullable)url completion:(CalendarCompletion)completion;


/**
 *  查Reminder提醒项
 *
 *  @param reminderIdentifierLocalKey    事件ID(标识符) 本地存储Key
 */
- (EKReminder *)checkCalendarReminderWithIdentifier:(NSString *)reminderIdentifierLocalKey;



/**
 *  查询Reminder提醒事项 (一段时间)(以dueDate为参照)
 *
 *  @param startDate  开始时间
 *  @param endDate    结束时间
 *  @param completion 回调方法
 */
- (void)checkCalendarIncompleteRemindersWithDueDateStarting:(nullable NSDate *)startDate endDate:(nullable NSDate *)endDate completion:(CalendarFetchDataCompletion)completion;


/**
 *  删除Reminber(删除单个)
 *
 *  @param reminderIdentifierLocalKey    reminder ID(标识符) 本地存储Key
 */
- (BOOL)deleteCalendarReminderWithIdentifier:(NSString *)reminderIdentifierLocalKey;



/**
 *  删除Reminder提醒事项 (一段时间)(以dueDate为参照)
 *
 *  @param startDate  开始时间
 *  @param endDate    结束时间
 *  @param completion 回调方法
 */

- (void)deleteCalendarIncompleteRemindersWithDueDateStarting:(nullable NSDate *)startDate endDate:(nullable NSDate *)endDate completion:(CalendarCompletion)completion;



/**
 *  修改Reminder提醒事项
 *
 *  @param reminderIdentifierLocalKey  事件ID 本地存储Key
 *  @param title      事件标题
 *  @param startDate  开始时间
 *  @param dueDate    预计结束时间
 *  @param alarmDate 闹钟Date
 *  @param notes      事件备注(传nil,则没有)
 *  @param url        事件url(传nil,则没有)
 *  @param completion 回调方法
 */
- (void)modifyCalendarReminderWithReminderIdentifierLocalKey:(NSString *)reminderIdentifierLocalKey calendarTitle:(NSString *)title notes:(NSString * __nullable)notes startDate:(NSDate *)startDate dueDate:(NSDate *)dueDate alarmDate:(NSDate *)alarmDate url:(NSURL * __nullable)url completion:(CalendarCompletion)completion;
@end

NS_ASSUME_NONNULL_END

XTCalendarEventManager.m

#import "XTCalendarEventManager.h"
#import "NSString+isBlank.h"
#import "XTAuthorityManager.h"
static NSString *const kEventCalendarIdentifierLocalKey = @"XT_CloudMeeting_EventCalendar_Identifier_Key";
static NSString *const kReminderCalendarIdentifierLocalKey = @"XT_CloudMeeting_ReminderCalendar_Identifier_Key";

static NSString *const kEventIdentifier = @"XT_Calendar_Event_Key";
static NSString *const kReminderIIdentifier = @"XT_Calendar_Reminder_Key";


@interface XTCalendarEventManager ()
@property (nonatomic,strong) EKEventStore *eventStore;
@property (nonatomic,strong) NSMutableArray <NSString *> *calendarItemIdentifierArray; // 存储日历唯一标识符
@end

@implementation XTCalendarEventManager
DEF_SINGLETON(XTCalendarEventManager);

- (instancetype)init {
    self = [super init];
    if (self) {
        self.eventStore = [[EKEventStore alloc] init];
        self.calendarItemIdentifierArray = [[NSMutableArray alloc] init];
        [self crateCalendar];
    }
    return self;
}

- (void)crateCalendar {
    EKCalendar *eventCalendar = [self findCustomCalendar:EKEntityTypeEvent];
    if (!eventCalendar) {
        [self createCustomCalendarWithEntityType:EKEntityTypeEvent title:@"云会议" completion:nil];
    }
    
    EKCalendar *reminderCalendar = [self findCustomCalendar:EKEntityTypeReminder];
    if (!reminderCalendar) {
        [self createCustomCalendarWithEntityType:EKEntityTypeReminder title:@"云会议" completion:nil];
    }
    

}

/**
 *  创建日历
 *
 *  @param entityType      日历类型 EKEntityTypeEvent EKEntityTypeReminder
 *  @param title      日历标题
 *  @param completion 回调方法
 */
- (void)createCustomCalendarWithEntityType:(EKEntityType)entityType title:(NSString *)title completion:(CalendarCompletion)completion {
    // 创建日历
    EKCalendar * customCalendar = [EKCalendar calendarForEntityType:entityType eventStore:self.eventStore];
    //  必须设置source,否则无法创建calendar
    customCalendar.source = [self getAvailableCalendarSource];
    customCalendar.title = title;
    
    NSError *error = nil;
    BOOL isSuccess = [self.eventStore saveCalendar:customCalendar commit:YES error:&error];
    
    if (!error) {
        if (![NSString isBlankString:customCalendar.calendarIdentifier]) {
            
            //存储calendarIdentifier
            NSString *calendarIdfLocalKey = nil;
            if (entityType == EKEntityTypeEvent) {
                calendarIdfLocalKey = kEventCalendarIdentifierLocalKey;
            }else{
                calendarIdfLocalKey = kReminderCalendarIdentifierLocalKey;
            }
            [XTUserDefault setValue:customCalendar.calendarIdentifier forKey:calendarIdfLocalKey];
            
        }else{
            isSuccess = NO;
            error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSLocalizedDescriptionKey : @"eventIdentifier不存在"}];
        }
    }else{
        isSuccess = NO;
    }
    
    !completion?:completion(isSuccess,error);
    
}

#pragma mark - 日历提醒 -
/**
 *  添加日历提醒事项
 *
 *  @param eventIdentifierLocalKey  事件ID 本地存储Key
 *  @param title      事件标题
 *  @param startDate  开始时间
 *  @param endDate    结束时间
 *  @param allDay     是否全天
 *  @param alarmArray 闹钟集合(传nil,则没有)
 *  @param notes      事件备注(传nil,则没有)
 *  @param url        事件url(传nil,则没有)
 *  @param completion 回调方法
 */
- (void)createCalendarEventWithEventIdentifierLocalKey:(NSString *)eventIdentifierLocalKey calendarTitle:(NSString *)title notes:(NSString * __nullable)notes startDate:(NSDate *)startDate endDate:(NSDate *)endDate allDay:(BOOL)allDay alarmArray:(NSArray <NSString *> * __nullable)alarmArray url:(NSURL * __nullable)url completion:(CalendarCompletion)completion {
    
    if (![self checkCaledarAuthority:EKEntityTypeEvent]) {
        NSError * authorityError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSLocalizedDescriptionKey : @"未授权"}];
        !completion?:completion(NO,authorityError);
        return;
    }
    
    
    BOOL isSuccess = NO;
    EKEvent *event = [EKEvent eventWithEventStore:self.eventStore];
    event.title = title;
    event.allDay = allDay;
    event.notes = notes;
    event.URL = url?:[NSURL URLWithString:@"xtcloudmeeting://"];
    event.startDate = startDate;
    event.endDate = endDate;
    
    //添加闹钟提醒
    if (alarmArray && alarmArray.count > 0) {
        for (NSString *timeString in alarmArray) {
            [event addAlarm:[EKAlarm alarmWithRelativeOffset:[timeString integerValue]]];
        }
    }
    
    // 存储到日历源中
    EKCalendar *eventCalendar = [self findCustomCalendar:EKEntityTypeEvent];
    if (eventCalendar) {
        [event setCalendar:eventCalendar];
    }else{
        [event setCalendar:[self.eventStore defaultCalendarForNewEvents]];
        NSLog(@"XTCalendarEventManager save defaultCalendarForNewEvents");
    }
    
    // 保存日历
    NSError *isError;
    isSuccess = [self.eventStore saveEvent:event span:EKSpanThisEvent commit:YES error:&isError];
    
    if (!isError) {
        if (![NSString isBlankString:event.eventIdentifier] && ![NSString isBlankString:eventIdentifierLocalKey]) {
            //存储日历ID
            [XTUserDefault setValue:event.eventIdentifier forKey:[self getCalendarItemLocalKey:eventIdentifierLocalKey type:EKEntityTypeEvent]];
        }else{
            isSuccess = NO;
            isError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSLocalizedDescriptionKey : @"eventIdentifier不存在"}];
        }
    }
    
    !completion?:completion(isSuccess,isError);
    
    NSLog(@"XTCalendarEventManager createCalendarEventWithEventIdentifierLocalKey %@",eventIdentifierLocalKey);
    
}

/**
 *  查日历事件
 *
 *  @param eventIdentifierLocalKey    事件ID(标识符) 本地存储Key
 */
- (EKEvent *)checkCalendarEventWithIdentifier:(NSString *)eventIdentifierLocalKey{
    
    if (![self checkCaledarAuthority:EKEntityTypeEvent]) {
        NSLog(@"XTCalendarEventManager checkCalendarEventWithIdentifier 未授权");
        return nil;
    }
    
    NSString *eIdentifier = [XTUserDefault getValueForKey:[self getCalendarItemLocalKey:eventIdentifierLocalKey type:EKEntityTypeEvent]];
    if (![NSString isBlankString:eIdentifier]) {
        EKEvent *event = [self.eventStore eventWithIdentifier:eIdentifier];
        return event;
    }
    return nil;
}

/**
 *  查日历事件(可查询一段时间内的事件)
 *
 *  @param startDate  开始时间
 *  @param endDate    结束时间
 *  @param modifytitle    标题,为空则都要查询
 */
- (NSArray *)checkCalendarEventWithStartDate:(NSDate *)startDate endDate:(NSDate *)endDate modifytitle:(NSString * __nullable)modifytitle {
    
    if (![self checkCaledarAuthority:EKEntityTypeEvent]) {
        NSLog(@"XTCalendarEventManager checkCalendarEventWithDate 未授权");
        return nil;
    }
    
    // 查询到所有的日历
    EKCalendar *eventCalendar = [self findCustomCalendar:EKEntityTypeEvent];
    
    if (!eventCalendar) {
        eventCalendar = [self.eventStore defaultCalendarForNewEvents];
    }
    
    NSPredicate *predicate = [self.eventStore predicateForEventsWithStartDate:startDate endDate:endDate calendars:@[eventCalendar]];
    
    // 获取到范围内的所有事件
    NSArray *request = [self.eventStore eventsMatchingPredicate:predicate];
    // 按开始事件进行排序
    request = [request sortedArrayUsingSelector:@selector(compareStartDateWithEvent:)];
    
    if ([NSString isBlankString:modifytitle]) {
        return request;
    }else{
        NSMutableArray *onlyRequest = [NSMutableArray array];
        for (int i = 0; i < request.count; i++) {
            EKEvent *event = request[i];
            if (event.title && [event.title isEqualToString:modifytitle]) {
                [onlyRequest addObject:event];
            }
        }
        return onlyRequest;
    }
}


/**
 *  删除日历事件(删除单个)
 *
 *  @param eventIdentifierLocalKey    事件ID(标识符) 本地存储Key
 */
- (BOOL)deleteCalendarEventWithIdentifier:(NSString *)eventIdentifierLocalKey{
    
    if (![self checkCaledarAuthority:EKEntityTypeEvent]) {
        NSLog(@"XTCalendarEventManager deleteCalendarEventWithIdentifier 未授权");
        return NO;
    }
    
    BOOL isDelete = NO;
    
    NSString *eIdentifier = [XTUserDefault getValueForKey:[self getCalendarItemLocalKey:eventIdentifierLocalKey type:EKEntityTypeEvent]];
    EKEvent *event;
    if (![NSString isBlankString:eIdentifier]) {
        event = [self.eventStore eventWithIdentifier:eIdentifier];
        isDelete = [self.eventStore removeEvent:event span:EKSpanThisEvent error:nil];
    }
    NSLog(@"XTCalendarEventManager deleteCalendarEventWithIdentifier %@ %d",eventIdentifierLocalKey,isDelete);
    return isDelete;
}

/**
 *  删除日历事件(可删除一段时间内的事件)
 *
 *  @param startDate  开始时间
 *  @param endDate    结束时间
 *  @param modifytitle    标题,为空则都要删除
 */
- (BOOL)deleteCalendarEventWithStartDate:(NSDate *)startDate endDate:(NSDate *)endDate modifytitle:(NSString * __nullable)modifytitle {
    
    if (![self checkCaledarAuthority:EKEntityTypeEvent]) {
        NSLog(@"XTCalendarEventManager deleteCalendarEventWithDate 未授权");
        return NO;
    }
    
    // 获取到此事件
    NSArray *request = [self checkCalendarEventWithStartDate:startDate endDate:endDate modifytitle:modifytitle];
    
    for (int i = 0; i < request.count; i ++) {
        // 删除这一条事件
        EKEvent *event = request[i];
        NSError*error =nil;
        // commit:NO:最后再一次性提交
        [self.eventStore removeEvent:event span:EKSpanThisEvent commit:NO error:&error];
    }
    //一次提交所有操作到事件库
    NSError *errored = nil;
    BOOL commitSuccess= [self.eventStore commit:&errored];
    return commitSuccess;
}

/**
 *  修改日历事件
 *
 *  @param eventIdentifierLocalKey 事件ID(标识符) 本地key
 *  @param title      修改事件标题
 *  @param startDate  修改开始时间
 *  @param endDate    修改结束时间
 *  @param allDay     修改是否全天
 *  @param alarmArray 修改闹钟集合
 *  @param notes      修改事件备注
 *  @param url        修改事件url
 *  @param completion 回调方法
 */
- (void)modifyCalendarEventWithEventIdentifierLocalKey:(NSString *)eventIdentifierLocalKey calendarTitle:(NSString *)title notes:(NSString * __nullable)notes startDate:(NSDate *)startDate endDate:(NSDate *)endDate allDay:(BOOL)allDay alarmArray:(NSArray <NSString *> * __nullable)alarmArray url:(NSURL * __nullable)url completion:(CalendarCompletion)completion{
    
    if (![self checkCaledarAuthority:EKEntityTypeEvent]) {
        NSError * authorityError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSLocalizedDescriptionKey : @"未授权"}];
        !completion?:completion(NO,authorityError);
        return;
    }
    
    // 获取到此事件
    EKEvent *event = [self checkCalendarEventWithIdentifier:eventIdentifierLocalKey];
    if (event) {
        BOOL isDelete = [self deleteCalendarEventWithIdentifier:eventIdentifierLocalKey];
        NSLog(@"XTCalendarEventManager modify delete %d",isDelete);
        [self createCalendarEventWithEventIdentifierLocalKey:eventIdentifierLocalKey calendarTitle:title notes:notes startDate:startDate endDate:endDate allDay:allDay alarmArray:alarmArray url:url completion:completion];
    }else{
        // 没有此条日历
        [self createCalendarEventWithEventIdentifierLocalKey:eventIdentifierLocalKey calendarTitle:title notes:notes startDate:startDate endDate:endDate allDay:allDay alarmArray:alarmArray url:url completion:completion];
        
    }
}

#pragma mark - Reminder 提醒 -
/**
 *  添加Reminder提醒事项
 *
 *  @param reminderIdentifierLocalKey  事件ID 本地存储Key
 *  @param title      事件标题
 *  @param startDate  开始时间
 *  @param dueDate    预计结束时间
 *  @param alarmDate 闹钟Date
 *  @param notes      事件备注(传nil,则没有)
 *  @param url        事件url(传nil,则没有)
 *  @param completion 回调方法
 */
- (void)createCalendarReminderWithReminderIdentifierLocalKey:(NSString *)reminderIdentifierLocalKey calendarTitle:(NSString *)title notes:(NSString * __nullable)notes startDate:(NSDate *)startDate dueDate:(NSDate *)dueDate alarmDate:(NSDate *)alarmDate url:(NSURL * __nullable)url completion:(CalendarCompletion)completion {
    
    if (![self checkCaledarAuthority:EKEntityTypeReminder]) {
        NSError * authorityError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSLocalizedDescriptionKey : @"未授权"}];
        !completion?:completion(NO,authorityError);
        return;
    }
    
    EKReminder *reminder = [EKReminder reminderWithEventStore:self.eventStore];
    reminder.title = title;
    reminder.notes = notes;
    reminder.URL = url?:[NSURL URLWithString:@"xtcloudmeeting://"];
    
    reminder.startDateComponents = [[NSCalendar currentCalendar] components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitHour|NSCalendarUnitMinute|NSCalendarUnitSecond fromDate:startDate];
    reminder.dueDateComponents = [[NSCalendar currentCalendar] components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitHour|NSCalendarUnitMinute|NSCalendarUnitSecond fromDate:dueDate];
    
    //添加闹钟提醒
    [reminder addAlarm:[EKAlarm alarmWithAbsoluteDate:alarmDate]];
    
    // 存储到日历源中
    EKCalendar *reminderCalendar = [self findCustomCalendar:EKEntityTypeReminder];
    if (reminderCalendar) {
        [reminder setCalendar:reminderCalendar];
    }else{
        [reminder setCalendar:self.eventStore.defaultCalendarForNewReminders];
    }
    
    NSError *error = nil;
    BOOL isSuccess = [self.eventStore saveReminder:reminder commit:YES error:&error];
    
    if (!error) {
        if (![NSString isBlankString:reminder.calendarItemIdentifier] && ![NSString isBlankString:reminderIdentifierLocalKey]) {
            [XTUserDefault setValue:reminder.calendarItemIdentifier forKey:[self getCalendarItemLocalKey:reminderIdentifierLocalKey type:EKEntityTypeReminder]];
        }else{
            isSuccess = NO;
            error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSLocalizedDescriptionKey : @"eventIdentifier不存在"}];
        }
        
    }else{
        isSuccess = NO;
    }
    
    !completion?:completion(isSuccess,error);
    
    NSLog(@"XTCalendarEventManager createCalendarReminderWithReminderIdentifierLocalKey %@",reminderIdentifierLocalKey);
    
}


/**
 *  查Reminder提醒项
 *
 *  @param reminderIdentifierLocalKey    事件ID(标识符) 本地存储Key
 */
- (EKReminder *)checkCalendarReminderWithIdentifier:(NSString *)reminderIdentifierLocalKey{
    
    if (![self checkCaledarAuthority:EKEntityTypeReminder]) {
        NSLog(@"XTCalendarEventManager checkCalendarReminderWithIdentifier 未授权");
        return nil;
    }
    NSString *eIdentifier = [XTUserDefault getValueForKey:[self getCalendarItemLocalKey:reminderIdentifierLocalKey type:EKEntityTypeReminder]];
    if (![NSString isBlankString:eIdentifier]) {
        EKCalendarItem *calendarItem = [self.eventStore calendarItemWithIdentifier:eIdentifier];
        if ([calendarItem isKindOfClass:[EKReminder class]]) {
            return (EKReminder *)calendarItem;
        }
        return nil;
    }
    return nil;
}

/**
 *  查询Reminder提醒事项 (一段时间)(以dueDate为参照)
 *
 *  @param startDate  开始时间
 *  @param endDate    结束时间
 *  @param completion 回调方法
 */
- (void)checkCalendarIncompleteRemindersWithDueDateStarting:(nullable NSDate *)startDate endDate:(nullable NSDate *)endDate completion:(CalendarFetchDataCompletion)completion{
    
    if (![self checkCaledarAuthority:EKEntityTypeReminder]) {
        !completion?:completion(nil);
        return;
    }
    
    EKCalendar *reminderCalendar = [self findCustomCalendar:EKEntityTypeReminder];
    
    if (!reminderCalendar) {
        reminderCalendar = [self.eventStore defaultCalendarForNewReminders];
    }
    
    NSPredicate *predicate = [self.eventStore predicateForIncompleteRemindersWithDueDateStarting:startDate ending:endDate calendars:@[reminderCalendar]];
    
    [self.eventStore fetchRemindersMatchingPredicate:predicate completion:^(NSArray<EKReminder *> * _Nullable reminders) {
        !completion?:completion(reminders);
    }];
}



/**
 *  删除Reminber(删除单个)
 *
 *  @param reminderIdentifierLocalKey    reminder ID(标识符) 本地存储Key
 */
- (BOOL)deleteCalendarReminderWithIdentifier:(NSString *)reminderIdentifierLocalKey {
    
    if (![self checkCaledarAuthority:EKEntityTypeReminder]) {
        NSLog(@"XTCalendarEventManager deleteCalendarReminderWithIdentifier 未授权");
        return NO;
    }
    
    BOOL isDelete = NO;
    EKReminder *reminder = [self checkCalendarReminderWithIdentifier:reminderIdentifierLocalKey];
    if (reminder) {
        isDelete = [self.eventStore removeReminder:reminder commit:YES error:nil];
    }
    NSLog(@"XTCalendarEventManager deleteCalendarReminderWithIdentifier %@ %d",reminderIdentifierLocalKey,isDelete);
    return isDelete;
}

/**
 *  删除Reminder提醒事项 (一段时间)(以dueDate为参照)
 *
 *  @param startDate  开始时间
 *  @param endDate    结束时间
 *  @param completion 回调方法
 */

- (void)deleteCalendarIncompleteRemindersWithDueDateStarting:(nullable NSDate *)startDate endDate:(nullable NSDate *)endDate completion:(CalendarCompletion)completion{
    
    if (![self checkCaledarAuthority:EKEntityTypeReminder]) {
        NSError * authorityError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSLocalizedDescriptionKey : @"未授权"}];
        !completion?:completion(NO,authorityError);
        return;
    }
    
    weakify_self
    [self checkCalendarIncompleteRemindersWithDueDateStarting:startDate endDate:endDate completion:^(NSArray *itemArray) {
        strongify_self
        for (int i=0; i<itemArray.count; i++) {
            EKReminder *tempReminder = (EKReminder *)[itemArray objectAtIndex:i];
            [self.eventStore removeReminder:tempReminder commit:NO error:nil];
        }
        NSError *error = nil;
        BOOL commitSucces = [self.eventStore commit:&error];
        !completion?:completion(commitSucces,error);
    }];
}

/**
 *  修改Reminder提醒事项
 *
 *  @param reminderIdentifierLocalKey  事件ID 本地存储Key
 *  @param title      事件标题
 *  @param startDate  开始时间
 *  @param dueDate    预计结束时间
 *  @param alarmDate 闹钟Date
 *  @param notes      事件备注(传nil,则没有)
 *  @param url        事件url(传nil,则没有)
 *  @param completion 回调方法
 */
- (void)modifyCalendarReminderWithReminderIdentifierLocalKey:(NSString *)reminderIdentifierLocalKey calendarTitle:(NSString *)title notes:(NSString * __nullable)notes startDate:(NSDate *)startDate dueDate:(NSDate *)dueDate alarmDate:(NSDate *)alarmDate url:(NSURL * __nullable)url completion:(CalendarCompletion)completion {
    
    if (![self checkCaledarAuthority:EKEntityTypeReminder]) {
        NSError * authorityError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSLocalizedDescriptionKey : @"未授权"}];
        !completion?:completion(NO,authorityError);
        return;
    }
    
    EKReminder *tempReminder = [self checkCalendarReminderWithIdentifier:reminderIdentifierLocalKey];
    if (tempReminder) {
        [self deleteCalendarReminderWithIdentifier:reminderIdentifierLocalKey];
        [self createCalendarReminderWithReminderIdentifierLocalKey:reminderIdentifierLocalKey calendarTitle:title notes:notes startDate:startDate dueDate:dueDate alarmDate:alarmDate url:url completion:completion];
    }else{
        [self createCalendarReminderWithReminderIdentifierLocalKey:reminderIdentifierLocalKey calendarTitle:title notes:notes startDate:startDate dueDate:dueDate alarmDate:alarmDate url:url completion:completion];
    }
    
}


#pragma mark - Pravite -
- (EKSource *)getAvailableCalendarSource {
    EKSource *localSource = nil;
    for (EKSource *source in self.eventStore.sources){
        if (source.sourceType == EKSourceTypeCalDAV && [source.title isEqualToString:@"iCloud"]){
            localSource = source;
            break;
        }
    }
    if (localSource == nil){
        for (EKSource *source in self.eventStore.sources){
            if (source.sourceType == EKSourceTypeLocal){
                localSource = source;
                break;
            }
        }
    }
    
    if (localSource == nil) {
        NSLog(@"XTCalendarEventManager getAvailableCalendarSource error");
    }
    
    return localSource;
}

- (EKCalendar *)findCustomCalendar:(EKEntityType)entityType {
    
    EKCalendar *customCalendar = nil;
    NSString *calendarIdentifer = nil;
    
    if (entityType == EKEntityTypeEvent) {
        calendarIdentifer = [XTUserDefault getValueForKey:kEventCalendarIdentifierLocalKey];
    }else{
        calendarIdentifer = [XTUserDefault getValueForKey:kReminderCalendarIdentifierLocalKey];
    }
    
    if (![NSString isBlankString:calendarIdentifer]) {
        NSArray *tempCalendarArray = [self.eventStore calendarsForEntityType:entityType];
        for (int i = 0 ; i < tempCalendarArray.count; i ++) {
            EKCalendar *temCalendar = tempCalendarArray[i];
            if ([temCalendar.calendarIdentifier isEqualToString:calendarIdentifer]) {
                customCalendar = temCalendar;
                break;
            }
        }
    }
    return customCalendar;

}

- (NSString *)getCalendarItemLocalKey:(NSString *)identifierString type:(EKEntityType)type {
    NSString *localKey = nil;
    if (type == EKEntityTypeEvent) {
        localKey = [NSString stringWithFormat:@"%@_%@",kEventIdentifier,identifierString];
    }else{
        localKey = [NSString stringWithFormat:@"%@_%@",kReminderIIdentifier,identifierString];
    }
    return localKey;
}

- (BOOL)checkCaledarAuthority:(EKEntityType)type {
    BOOL isAuthority = NO;
    XTAuthorityStatus status = XTAuthorityStatusNotDetermined;
    if (type == EKEntityTypeEvent) {
        status = [[XTAuthorityManager sharedInstance] checkCalendarAuthority];
    }else {
        status = [[XTAuthorityManager sharedInstance] checkReminderAuthority];
    }
    
    if (status == XTAuthorityStatusAuthorized) {
        isAuthority = YES;
    }
    return isAuthority;
    
}
@end
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容