前言
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作为判断,加上了日历源)