iOS增、删、改、查系统提醒事件和日历事件

前言

1、引入EventKit框架。 (EventKit框架使你能访问用户的Calendar(日历)和Reminder(提醒事项)信息。他们使用相同的库(EKEventStore)处理数据,该库管理所有event数据。该框架除了允许检索用户已经存在的calendar和reminder数据外,还允许创建新的事件和提醒)
2、将EKEventStore对象写成单例。 (因为需要频繁调用EKEventStore对象,可以将EKEventStore对象写成单例)
3、设置访问日历的权限NSCalendarsUsageDescription。(iOS10之后,要用到某个权限必须在info.plist里指明,否则会引起崩溃和审核失败)

检测日历功能是否可以使用

  • 检查授权状态:
EKAuthorizationStatus eventStatus = [EKEventStore  authorizationStatusForEntityType:EKEntityTypeEvent];
其中eventStatus为
EKAuthorizationStatusNotDetermined = 0,// 未进行授权选择
EKAuthorizationStatusRestricted,//未授权,且用户无法更新,如家长控制情况下
EKAuthorizationStatusDenied,       // 用户拒绝App使用
EKAuthorizationStatusAuthorized,   // 已授权,可使用
  • 在“未进行授权”的情况下弹出提示框,提示用户授权:
[self.eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError * _Nullable error) {
      
 }];

完整代码:

// 检测日历功能是否可以使用
- (void)checkCalendarCanUsedCompletion:(completion)completion{
    //    EKEntityTypeEvent日历事件
    //    EKEntityTypeReminder提醒事项
    self.completion = completion;
    EKAuthorizationStatus eventStatus = [EKEventStore  authorizationStatusForEntityType:EKEntityTypeEvent];
    if (eventStatus == EKAuthorizationStatusAuthorized) {
        // 已授权,可使用
        if (self.completion) {
            self.completion(YES, nil);
        }
    }else if(eventStatus == EKAuthorizationStatusNotDetermined){
        // 未进行授权选择
        __block  BOOL isGranted = NO;
        __block  NSError *isError;
        [self.eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError * _Nullable error) {
            isGranted = granted;
            isError = error;
            if (granted) {
                NSLog(@"用户点击了允许访问日历");
            }else{
                NSLog(@"用户没有点允许访问日历");
            }
            if(self.completion){
                self.completion(isGranted, isError);
            }
        }];
    }else{
        // 未授权
        NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:@{NSLocalizedDescriptionKey : @"未授权"}];
        if (self.completion) {
            self.completion(NO, error);
        }
    }
}

.

添加日历源

  • 将日历事件添加到默认的日历源中
[event setCalendar:[self.eventStore defaultCalendarForNewEvents]];
  • 新增日历源
EKCalendar *eventCalendar = [EKCalendar calendarForEntityType:EKEntityTypeEvent eventStore:self.eventStore];
eventCalendar.source = localSource;

注:其中source需要分别进行对Local和iCloud两次判断,否则可能会出现source为空的错误提示,因为在iCloud可用的时候,Local的calendar是隐藏的,具体在“设置->账户->iCloud->使用iCloud的应用”中查看日历是否开启iCloud

完整代码:

/**
 *  添加日历源
 *
 *  @param calendarIdentifier  日历源ID(标识符,用于区分日历源)
 *  @param title      日历源标题
 *  @param completion 回调方法
 */
- (void)createCalendarIdentifier:(NSString *)calendarIdentifier addCalendarTitle:(NSString *)title addCompletion:(completion)completion{
    self.completion = completion;
    NSError *error;
    BOOL isSuccess = NO;
    EKSource *localSource;
    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;
            }
        }
    }
    EKCalendar *eventCalendar = [EKCalendar calendarForEntityType:EKEntityTypeEvent eventStore:self.eventStore];
    eventCalendar.source = localSource;
    eventCalendar.title = title;
    isSuccess = [self.eventStore saveCalendar:eventCalendar commit:YES error:&error];
    if (!error) {
        if (eventCalendar.calendarIdentifier && ![eventCalendar.calendarIdentifier isEqualToString:@""] && calendarIdentifier && ![calendarIdentifier isEqualToString:@""]) {
            //存储日历ID
            NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
            [userDefaults setObject:eventCalendar.calendarIdentifier forKey:calendarIdentifier];
            isSuccess = [userDefaults synchronize];
            if (!isSuccess) {
                error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSLocalizedDescriptionKey : @"存储失败"}];
            }
        }else{
            error = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSLocalizedDescriptionKey : @"eventIdentifier不存在"}];
        }
    }
    if (self.completion) {
        self.completion(isSuccess, error);
    }
}

添加日历提醒事项

  • 保存日历事件:
[self.eventStore saveEvent:event span:EKSpanThisEvent commit:YES error:&err]; 

commit:yes:表示立即把此次操作提交到系统事件库,NO表示此时不提交。如果一次性操作的事件数比较少的话,可以每次都传YES,实时更新事件数据库。如果一次性操作的事件较多的话,可以每次传NO,最后再执行一次提交所有更改到数据库,把原来的更改全部提交到数据库,不管是添加还是删除。
EKSpanThisEvent:表示只影响当前事件。 EKSpanFutureEvents 表示影响当前和以后的所有事件。比如某条重复任务修改后保存时,传EKSpanThisEvent表示值修改这一条重复事件。传EKSpanFutureEvents表示修改这一条和以后的所有重复事件。删除事件时,分别表示删除这一条;删除这一条和以后的所有。

  • 用NSUserDefaults对eventIdentifier进行存储
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:event.eventIdentifier forKey:eventIdentifier];
isGranted = [userDefaults synchronize];

完整代码:

- (void)createEventIdentifier:(NSString *)eventIdentifier addCalendarTitle:(NSString *)title addLocation:(NSString *)location addStartDate:(NSDate *)startDate addEndDate:(NSDate *)endDate addAllDay:(BOOL)allDay addAlarmArray:(NSArray *)alarmArray addNotes:(NSString *)notes addURL:(NSURL *)url addCalendarIdentifier:(NSString *)calendarIdentifier addCompletion:(completion)completion{
    self.completion = completion;
    __block  BOOL isGranted = NO;
    __block  NSError *isError;
    if ([self.eventStore respondsToSelector:@selector(requestAccessToEntityType:completion:)]){
        [self.eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error){
            isGranted = granted;
            isError = error;
            dispatch_async(dispatch_get_main_queue(), ^{
                if (isError){
                    if (self.completion) {
                        self.completion(isGranted,isError);
                    }
                }else if (!isGranted){
                    if (self.completion) {
                        self.completion(isGranted,isError);
                    }
                }else{
                    EKEvent *event  = [EKEvent eventWithEventStore:self.eventStore];
                    event.title = title;
                    event.location = location;
                    
                    NSDateFormatter *tempFormatter = [[NSDateFormatter alloc]init];
                    [tempFormatter setDateFormat:@"dd.MM.yyyy HH:mm"];
                    
                    event.startDate = startDate;
                    event.endDate   = endDate;
                    // 是否设置全天
                    event.allDay = allDay;
                    if (notes && ![notes isEqualToString:@""]) {
                        event.notes = notes;
                    }
                    if(url){
                        event.URL = url;
                    }
                    //添加提醒
                    if (alarmArray && alarmArray.count > 0) {
                        for (NSString *timeString in alarmArray) {
                            [event addAlarm:[EKAlarm alarmWithRelativeOffset:[timeString integerValue]]];
                            
                        }
                    }
                    // 存储到源中
                    EKCalendar *eventCalendar;
                    NSString *cIdentifier = [[NSUserDefaults standardUserDefaults] objectForKey:calendarIdentifier];
                    if (cIdentifier && ![cIdentifier isEqualToString:@""]) {
                        NSArray *tempA = [self.eventStore calendarsForEntityType:EKEntityTypeEvent];
                        for (int i = 0 ; i < tempA.count; i ++) {
                            EKCalendar *temCalendar = tempA[i];
                            if ([temCalendar.calendarIdentifier isEqualToString:cIdentifier]) {
                                eventCalendar = temCalendar;
                            }
                        }
                    }
                    if (eventCalendar) {
                        [event setCalendar:eventCalendar];
                    }else{
                        [event setCalendar:[self.eventStore defaultCalendarForNewEvents]];
                    }
                    
                    // 保存日历
                    [self.eventStore saveEvent:event span:EKSpanThisEvent commit:YES error:&isError];
                    if (!isError) {
                        if (event.eventIdentifier && ![event.eventIdentifier isEqualToString:@""] && eventIdentifier && ![eventIdentifier isEqualToString:@""]) {
                            //存储日历ID
                            NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
                            [userDefaults setObject:event.eventIdentifier forKey:eventIdentifier];
                            isGranted = [userDefaults synchronize];
                            if (!isGranted) {
                                isError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSLocalizedDescriptionKey : @"存储失败"}];
                            }
                        }else{
                            isError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{NSLocalizedDescriptionKey : @"eventIdentifier不存在"}];
                        }
                    }
                    if (self.completion) {
                        self.completion(isGranted,isError);
                    }
                }
            });
        }];
    }
}
    其中EKEvent属性:
    title - 日程的标题
    startDate - 日程的开始日期
    endDate - 日程的结束日期
    calendar - 日程对应的日历
    alarms - 日程的闹钟提醒
    eventIdentifier:唯一标识符区分某个事件,只读
    notes - 事件备注
    url - 事件url
    calendarIdentifier - 事件源
    recurrenceRules - 重复规则,默认不重复
    -  alarms:
    通过event的addAlarm:方法为一个日程添加提醒,指定一个确切时间或一个相对时间。     
    通过removeAlarm: 方法可将提醒移除。
    -  recurrenceRules:
    用initRecurrenceWithFrequency:interval:end:方法创建一个EKRecurrenceRule对象。
    1)EKRecurrenceFrequency类型,指示recurrence是daily、weekly、monthly或yearly.
    2)  interval: 这是一个大于0的整数,来制定重复的间隔。例如,如果recurrence rule是
        weekly,并且recurrence interval是1,然后重复模式为每星期都重复。如果
       recurrence interval是3,那么是每3个星期重复一次。    
    3) end: 这是个可选参数,代表何时终止。

.

查询日历事件

  • 查询所有的日历事件,包括家庭,工作,生日,中国节假日类型等
NSArray *tempA = [self.eventStore calendarsForEntityType:EKEntityTypeEvent];
  • 谓词查询
NSPredicate *predicate = [self.eventStore predicateForEventsWithStartDate:startDate endDate:endDate calendars:only3D];
// 获取到范围内的所有事件
NSArray *request = [self.eventStore eventsMatchingPredicate:predicate];

EKEventStore的eventsMatchingPredicate:方法获取属于你提供的谓词中指定的日期范围的所有事件。

  • 单个日历事件查询
EKEvent *event = [self.eventStore eventWithIdentifier:eIdentifier];

完整代码
(modifytitle : 有值则查询此日期内标题为modifytitle的日历事件,无则查询此日期内全部日历事件,calendarIdentifier : 日历源,有值则查询此日期内日历源为calendarIdentifier的日历事件,无则查询此日期内默认日历源事件)

/**
 *  查日历事件(可查询一段时间内的事件)
 *
 *  @param startDate  开始时间
 *  @param endDate    结束时间
 *  @param modifytitle    标题,为空则都要查询
 *  @param calendarIdentifier  事件源(传nil,则为默认)
 */
- (NSArray *)checkToStartDate:(NSDate *)startDate addEndDate:(NSDate *)endDate addModifytitle:(NSString *)modifytitle addCalendarIdentifier:(NSString *)calendarIdentifier{
    
    // 查询到所有的日历
    NSArray *tempA = [self.eventStore calendarsForEntityType:EKEntityTypeEvent];
    NSMutableArray *only3D = [NSMutableArray array];
    
    for (int i = 0 ; i < tempA.count; i ++) {
        
        EKCalendar *temCalendar = tempA[i];
        EKCalendarType type = temCalendar.type;
        // 工作、家庭和本地日历
        if (type == EKCalendarTypeLocal || type == EKCalendarTypeCalDAV)  {
            if (calendarIdentifier && ![calendarIdentifier isEqualToString:@""]) {
                NSString *cIdentifier = [[NSUserDefaults standardUserDefaults] objectForKey:calendarIdentifier];
                if ([temCalendar.calendarIdentifier isEqualToString:cIdentifier]){
                    [only3D addObject:temCalendar];
                }
            }else{
                [only3D addObject:temCalendar];
            }
        }
    }
    
    NSPredicate *predicate = [self.eventStore predicateForEventsWithStartDate:startDate endDate:endDate calendars:only3D];
    
    // 获取到范围内的所有事件
    NSArray *request = [self.eventStore eventsMatchingPredicate:predicate];
    // 按开始事件进行排序
    request = [request sortedArrayUsingSelector:@selector(compareStartDateWithEvent:)];
    
    if (!modifytitle || [modifytitle isEqualToString:@""]) {
        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 eventIdentifier    事件ID(标识符)
 */
- (EKEvent *)checkToEventIdentifier:(NSString *)eventIdentifier{
    NSString *eIdentifier = [[NSUserDefaults standardUserDefaults] objectForKey:eventIdentifier];
    if (eIdentifier && ![eIdentifier isEqualToString:@""]) {
        EKEvent *event = [self.eventStore eventWithIdentifier:eIdentifier];
        return event;
    }
    return nil;
}

删除日历事件

  • 删除日历事件
// commit:NO:最后再一次性提交,YES:当前提交等同:
// [self.eventStore removeEvent:event span:EKSpanThisEvent error:&error];
 [self.eventStore removeEvent:event span:EKSpanThisEvent commit:NO error:&error];
  • 一次提交所有操作到事件库
NSError *errored = nil;
BOOL commitSuccess= [self.eventStore commit:&errored];
return commitSuccess;

完整代码:

/**
 *  删除日历事件(删除单个)
 *
 *  @param eventIdentifier    事件ID(标识符)
 */
- (BOOL)deleteCalendarEventIdentifier:(NSString *)eventIdentifier{
    NSString *eIdentifier = [[NSUserDefaults standardUserDefaults] objectForKey:eventIdentifier];
    EKEvent *event;
    NSError*error =nil;
    if (eIdentifier && ![eIdentifier isEqualToString:@""]) {
        event = [self.eventStore eventWithIdentifier:eIdentifier];
        return [self.eventStore removeEvent:event span:EKSpanThisEvent error:&error];
    }
    return NO;
}


/**
 *  删除日历事件(可删除一段时间内的事件)
 *
 *  @param startDate  开始时间
 *  @param endDate    结束时间
 *  @param modifytitle    标题,为空则都要删除
 *  @param calendarIdentifier  事件源(传nil,则为默认)
 */
- (BOOL)deleteCalendarStartDate:(NSDate *)startDate addEndDate:(NSDate *)endDate addModifytitle:(NSString *)modifytitle addCalendarIdentifier:(NSString *)calendarIdentifier{
    // 获取到此事件
    NSArray *request = [self checkToStartDate:startDate addEndDate:endDate addModifytitle:modifytitle addCalendarIdentifier:calendarIdentifier];
    
    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;
}

.

修改日历事件

查询是否有此日历,没有则添加,有则先删除后添加来实现修改
完整代码

// 修改日历
- (void)modifyEventIdentifier:(NSString *)eventIdentifier addTitle:(NSString *)title addLocation:(NSString *)location addStartDate:(NSDate *)startDate addEndDate:(NSDate *)endDate addAllDay:(BOOL)allDay addAlarmArray:(NSArray *)alarmArray addNotes:(NSString *)notes addURL:(NSURL *)url addCIdentifier:(NSString *)cIdentifier addCompletion:(completion)completion{
    // 获取到此事件
    EKEvent *event = [self checkToEventIdentifier:eventIdentifier];
    if (event) {
        [self deleteCalendarEventIdentifier:eventIdentifier];
        
        [self createEventIdentifier:eventIdentifier addCalendarTitle:title addLocation:location addStartDate:startDate addEndDate:endDate addAllDay:allDay addAlarmArray:alarmArray addNotes:notes addURL:url addCalendarIdentifier:cIdentifier addCompletion:completion];
    }else{
        // 没有此条日历
        [self createEventIdentifier:eventIdentifier addCalendarTitle:title addLocation:location addStartDate:startDate addEndDate:endDate addAllDay:allDay addAlarmArray:alarmArray addNotes:notes addURL:url addCalendarIdentifier:cIdentifier addCompletion:completion];
    }
}

工具类(ViewController是注释掉的例子)
WMEventCalendarDemo
代码注:4.9号重新修改过(将eventIdentifier作为判断,加上了日历源)

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

推荐阅读更多精彩内容