EKEventStore
EKEventStore
用于应用访问日历或者提醒事件,在iOS中,创建好实例之后,必须使用requestAccessToEntityType:completion:
方法访问实体类型。
与一个EKEventStore
实例相关联的事件,提醒,日历数据无法在另一个EKEventStore
实例中使用,所以你最好创建一个常驻的实例,例如创建一个单例。
在iOS10.0或者之后的版本,你必须在info.plist文件中添加权限请求,否则应用将会崩溃。提醒和日历分别是NSRemindersUsageDescription
和NSCalendarsUsageDescription
。
在macOS中,使用initWithAccessToEntityTypes:
代替默认初始化方法,可以接受的实体类型是EKEntityMaskEvent
和EKEntityMaskReminder
1 2 3 4 5 6 7 8 9 10 11 12 13
| // 创建实例 EKEventStore *eventStore = [[EKEventStore alloc] init]; self.eventStore = eventStore;
// 获取数据 [self.eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError * _Nullable error) { if (granted) { NSLog(@"授权通过"); // 下一步操作... } else { NSLog(@"授权没有通过"); } }];
|
获取授权状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| // 获取授权状态 EKAuthorizationStatus eventStatus = [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent]; switch (eventStatus) { case EKAuthorizationStatusNotDetermined:// 未进行选择 { // 弹框 提示授权 @weakify(self) [self.eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError * _Nullable error) { @strongify(self) if (granted) { NSLog(@"授权通过"); // 下一步操作... } else { NSLog(@"授权没有通过"); } }]; }break; case EKAuthorizationStatusRestricted:// 未授权 { NSLog(@"未授权"); }break; case EKAuthorizationStatusDenied:// 拒绝 { NSLog(@"拒绝"); }break; case EKAuthorizationStatusAuthorized:// 已授权 { NSLog(@"已授权"); }break; default:break; }
|
EKSource 日历源
你不需要创建此类的实例; 相反,你从EKEventStore
实例中获取EKSource
对象。 使用sources
属性获取事件存储的所有EKSource
对象,并使用此类中的实例方法访问该对象。
EKSource
为日历所属的集合的抽象超类。一个EKEventStore
可以包含多个EKSource
,一个日历源可以包含多个日历对象。
日历的类型:
1 2 3 4 5 6 7 8
| typedef NS_ENUM(NSInteger, EKSourceType) { EKSourceTypeLocal, EKSourceTypeExchange, EKSourceTypeCalDAV,//Represents a CalDAV or iCloud source EKSourceTypeMobileMe, EKSourceTypeSubscribed, EKSourceTypeBirthdays };
|

获取指定的日历源
1 2 3 4 5 6 7
| NSArray *sources = self.eventStore.sources; for (EKSource *source in sources) { if (source.sourceType == EKSourceTypeCalDAV) { // 当前source的类型为EKSourceTypeCalDAV,你还可以使用title属性进行筛选 break; } }
|
EKCalendar 日历
type
这个属性跟所属于的EKSource
实例的type对应
calendarIdentifier
,我们可以根据这个属性,使用EKEventStore
的实例方法来获取指定的EKCalendar
对象。需要注意的是,当发生了完全同步
之后,这个值是会变化的。
会发生完全同步
的几种情况:
- 在iPhone上,用户手动修改了日历的名字,添加/编辑/删除事件
- 用户在mac上对iCloud日历进行了一些修改,iOS设备会通知iCloud日历已更改,从而发生同步
- 第三方应用程序收到日历通知,iOS在后台启动,应用根据通知创建一些日历事件
也就是说,完全同步
事件可以随时发生。当发生完全同步
时,你可以通过下面的代码获得通知
1 2 3 4 5
| [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(storeChanged:) name:EKEventStoreChangedNotification object:eventStore];
|
如果需要的话,收到通知之后,重新创建EKCalendar
实例是有意义的。
创建添加日历
创建日历之前,我们先要找到它所属于的日历源(当然这取决与你想把它放在哪个日历源)。创建时,我们先要判断是否已经有相同名字的日历存在,如果没有的话,则继续创建。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| - (void)checkJYCalendar { NSArray *sources = self.eventStore.sources; for (EKSource *source in sources) { if (source.sourceType == EKSourceTypeCalDAV) { NSSet *calendars = [source calendarsForEntityType:EKEntityTypeEvent]; BOOL needAdd = true; for (EKCalendar *calendar in calendars) { if ([calendar.title isEqualToString:@"测试日历"]) { needAdd = false; _calendar = calendar; break; } } // 如果没找到日历,则创建一个 if (needAdd) { EKCalendar *calendar = [EKCalendar calendarForEntityType:EKEntityTypeEvent eventStore:self.eventStore]; calendar.source = source; calendar.title = @"测试日历"; calendar.CGColor = [UIColor greenColor].CGColor; NSError *error; [self.eventStore saveCalendar:calendar commit:YES error:&error]; if (error) { JYLog(@"创建日历失败 error:%@",error); } else { JYLog(@"创建日历成功"); } _calendar = calendar; } break; } } }
|
获取日历集
可以用下面两种实例方法获取日历数据
1 2 3 4 5 6
| // EKEventStore的实例方法 - (NSArray<EKCalendar *> *)calendarsForEntityType:(EKEntityType)entityType NS_AVAILABLE(10_8, 6_0);
// EKSource的实例方法 - (NSSet<EKCalendar *> *)calendarsForEntityType:(EKEntityType)entityType NS_AVAILABLE(10_8, 6_0);
|
EkEvent 日历事件
代表添加到日历中的事件。使用类方法eventWithEventStore:
来创建实例
属性说明
eventIdentifier
:您可以使用此标识符使用EKEventStore
方法eventWithIdentifier:
查找事件。
如果事件的日历发生更改,则其标识符很可能也会更改。
allDay
:设置是否是全天时间
startDate
:事件开始时间
endDate
:事件结束时间
structuredLocation
:往事件里添加地理信息位置,get方法仅返回地理位置的名称
如果你创建了一个日历事件,并且设置了重复规则:从2019-01-01开始,连续10天,每天9:00-9:30。这就就相当于你创建了10个日历事件!对每天的日历事件来说,9:00即·startDate·,9:30即·endDate·,也不是说2019-01-10 9:30是·endDate·!
事件修改
当你对EKEvent
的属性修改完成之后,可以用EKEventStore
的方法- (BOOL)saveEvent:(EKEvent *)event span:(EKSpan)span commit:(BOOL)commit error:(NSError **)error
进行保存
- EKSpan有两种类型,EKSpanThisEvent和EKSpanFutureEvents
举个例子,你创建了一个·EkEvent·对象,并且添加到了日历中了。它的规则为:连续10天,每天的早上9:00- 9:30。当你使用·EKEventStore·的方法·eventsMatchingPredicate:·查找时间时,就会得到10个·EKEventStore·对象,如果是事件发生顺序的快慢来编号(1-10),那么对于第一个·EkEvent·对象讲,·EKSpanThisEvent·指的是自己,而·EKSpanFutureEvents·指的是编号为(1-10)的几个对象;对于第二个·EkEvent·对象讲,·EKSpanThisEvent·指的是自己,而·EKSpanFutureEvents·指的是编号为(2-10)的几个对象。总之,最好自己尝试一下看看这到底是什么。
事件刷新
当在其它地方对事件进行了修改之后,你可以收到EKEventStoreChangedNotification
的通知,这个时候你可以选择使用实例方法refresh
,更新事件的数据
在日历中添加事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| /** 添加事件到日历中 */ - (BOOL)addEventToCalendarWithTitle:(NSString *)title location:(NSString *)location startDate:(NSDate *)start endDate:(NSDate *)end alarmArray:(NSArray *)alarmArray { EKEvent *newEvent = [EKEvent eventWithEventStore:self.eventStore]; newEvent.title = title; newEvent.startDate = start; newEvent.endDate = end; newEvent.location = location; newEvent.notes = @"这是备注"; newEvent.allDay = false; // 重复规则, 具体代码在下面会讲到 EKRecurrenceRule *rule = [self getRecurrenceRule]; // 设置提醒,具体代码在下面会讲到 [newEvent addAlarm:[self getAlarm]]; newEvent.calendar = self.calendar; NSError *error = nil; [self.eventStore saveEvent:newEvent span:EKSpanThisEvent commit:YES error:&error]; if (isNull(error)) { JYLog(@"添加Event成功"); return YES; } else { JYLog(@"添加Event失败 error:%@", error); return NO; } }
|
EKRecurrenceRule重复周期
目前无法直接修改EKRecurrenceRule或其任何属性。 通过创建新的EKRecurrenceRule并设置事件或提醒以使用新创建的重复规则。
一般使用方法下面的方法来创建实例:
1 2 3 4 5 6 7 8 9
| - (instancetype)initRecurrenceWithFrequency:(EKRecurrenceFrequency)type interval:(NSInteger)interval daysOfTheWeek:(nullable NSArray<EKRecurrenceDayOfWeek *> *)days daysOfTheMonth:(nullable NSArray<NSNumber *> *)monthDays monthsOfTheYear:(nullable NSArray<NSNumber *> *)months weeksOfTheYear:(nullable NSArray<NSNumber *> *)weeksOfTheYear daysOfTheYear:(nullable NSArray<NSNumber *> *)daysOfTheYear setPositions:(nullable NSArray<NSNumber *> *)setPositions end:(nullable EKRecurrenceEnd *)end;
|
type
: 重复规则的频率,可以是每天,每周,每周和每年
interval
: 周期间隔,必须大于0
days
: 事件发生在一周的哪几天,是一个包含了EKRecurrenceDayOfWeek
对象的数组
monthDays
: 事件发生在一月的哪几天,值必须在131或者-1-31之间,当为-1时,即代表这个月的倒数第一天,这个参数仅当type
为EKRecurrenceFrequencyMonthly
有效
months
: 事件发生在一年的哪几月,值必须在112或者-1-12之间,当为-1时,即代表这年的倒数第一个月,这个参数仅当type
为EKRecurrenceFrequencyYearly
有效
weeksOfTheYear
: 事件发生在一年的哪几周,值必须在153或者-1-53之间,当为-1时,即代表这年的倒数第一周,这个参数仅当type
为EKRecurrenceFrequencyYearly
有效
daysOfTheYear
: 事件发生在一年的哪几天,值必须在1366或者-1-366之间,当为-1时,即代表这年的倒数第一天,这个参数仅当type
为EKRecurrenceFrequencyYearly
有效
setPositions
: 额外的重复条件
end
: 结束规则,为空时即代表永久重复
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| EKRecurrenceRule *rule = nil; // 每两天执行一次 rule = [[EKRecurrenceRule alloc] initRecurrenceWithFrequency:EKRecurrenceFrequencyDaily interval:2 end:nil]; // 工作日都执行 rule = [[EKRecurrenceRule alloc] initRecurrenceWithFrequency:EKRecurrenceFrequencyWeekly interval:1 daysOfTheWeek:@[[EKRecurrenceDayOfWeek dayOfWeek:2], [EKRecurrenceDayOfWeek dayOfWeek:3], [EKRecurrenceDayOfWeek dayOfWeek:4], [EKRecurrenceDayOfWeek dayOfWeek:5], [EKRecurrenceDayOfWeek dayOfWeek:6]] daysOfTheMonth:nil monthsOfTheYear:nil weeksOfTheYear:nil daysOfTheYear:nil setPositions:nil end:nil]; // 每两周的周1执行 rule = [[EKRecurrenceRule alloc] initRecurrenceWithFrequency:EKRecurrenceFrequencyWeekly interval:2 daysOfTheWeek:@[[EKRecurrenceDayOfWeek dayOfWeek:2]] daysOfTheMonth:nil monthsOfTheYear:nil weeksOfTheYear:nil daysOfTheYear:nil setPositions:nil end:nil]; // 在每年的第二周以及最后一周的工作执行 rule = [[EKRecurrenceRule alloc] initRecurrenceWithFrequency:EKRecurrenceFrequencyYearly interval:1 daysOfTheWeek:@[[EKRecurrenceDayOfWeek dayOfWeek:2], [EKRecurrenceDayOfWeek dayOfWeek:3], [EKRecurrenceDayOfWeek dayOfWeek:4], [EKRecurrenceDayOfWeek dayOfWeek:5], [EKRecurrenceDayOfWeek dayOfWeek:6]] daysOfTheMonth:nil monthsOfTheYear:nil weeksOfTheYear:nil daysOfTheYear:nil setPositions:@[@2, @-1] end:nil]; EKEvent *event = [[EKEvent alloc] init]; [event addRecurrenceRule:rule];
|
EKAlarm创建提醒
EKAlarm
在Event Kit
中代表警告。你可以使用alarmWithAbsoluteDate
和alarmWithAbsoluteDate
来创建实例。
可以用下面的方法来创建实例
1 2 3 4
| // 设置绝对时间 + (EKAlarm *)alarmWithAbsoluteDate:(NSDate *)date; // 设置相对时间(相对event的start date),单位是秒,设置负值表示事件前提醒,设置正值是事件发生后提醒 + (EKAlarm *)alarmWithRelativeOffset:(NSTimeInterval)offset;
|
设置了提醒后,我们打开iOS系统自带的日历App,会发现只会显示2个提醒,看不到多余的提醒.但是实际测试发现全部提醒都可以工作,而且我们可以在Mac的日历程序中看到所有的提醒。
属性说明:
structuredLocation
:与proximity
配合使用,简单来说,就是当你进入或者离开某个范围时,就可以发出提醒
1 2 3 4
| EKAlarm *alarm = nil; // 事件前5分钟提醒 alarm = [EKAlarm alarmWithRelativeOffset:-60. * 5];
|
结束
目前我对系统的日历讲解就到这啦,受限于个人的技术水平以及没有深度的使用,可能会有点错误的地方,希望指正,以后我发现错误之后也会及时来更新的!