Eventkit向日历和提醒事项中加入事件和闹铃
主要使用Eventkit事件库的EKEventStore类
该类负责日历和提醒事件的管理,可以简单理解为数据库,可以对事件进行增删改查。
因为它就像数据库一样,频繁的开启,关闭会影响效率,所以如果你的程序需要频繁操作日历和提醒,建议仅生成该对象一次,仅用一个对象进行操作。
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad{
[super viewDidLoad];
[self addEventNotify:[NSDate dateWithTimeIntervalSinceNow:60] title:@"测试事件"];
}
-(void)addEventNotify:(NSDate *)date title:(NSString *)title
{
//生成事件数据库对象
EKEventStore *eventDB = [[EKEventStore alloc] init];
//申请事件类型权限
[eventDB requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError * _Nullable error) {
if (granted) { //授权是否成功
EKEvent *myEvent = [EKEvent eventWithEventStore:eventDB]; //创建一个日历事件
myEvent.title = title; //标题
myEvent.startDate = date; //开始date required
myEvent.endDate = date; //结束date required
[myEvent addAlarm:[EKAlarm alarmWithAbsoluteDate:date]]; //添加一个闹钟 optional
[myEvent setCalendar:[eventDB defaultCalendarForNewEvents]]; //添加calendar required
NSError *err;
[eventDB saveEvent:myEvent span:EKSpanThisEvent error:&err]; //保存
}
}];
}
-(void)addReminderNotify:(NSDate *)date title:(NSString *)title
{
EKEventStore *eventDB = [[EKEventStore alloc] init];
//申请提醒权限
[eventDB requestAccessToEntityType:EKEntityTypeReminder completion:^(BOOL granted, NSError * _Nullable error) {
if (granted) {
//创建一个提醒功能
EKReminder *reminder = [EKReminder reminderWithEventStore:eventDB];
//标题
reminder.title = title;
//添加日历
[reminder setCalendar:[eventDB defaultCalendarForNewReminders]];
NSCalendar *cal = [NSCalendar currentCalendar];
[cal setTimeZone:[NSTimeZone systemTimeZone]];
NSInteger flags = NSCalendarUnitYear | NSCalendarUnitMonth |
NSCalendarUnitDay |NSCalendarUnitHour | NSCalendarUnitMinute |
NSCalendarUnitSecond;
NSDateComponents* dateComp = [cal components:flags fromDate:date];
dateComp.timeZone = [NSTimeZone systemTimeZone];
reminder.startDateComponents = dateComp; //开始时间
reminder.dueDateComponents = dateComp; //到期时间
reminder.priority = 1; //优先级
EKAlarm *alarm = [EKAlarm alarmWithAbsoluteDate:date]; //添加一个车闹钟
[reminder addAlarm:alarm];
NSError *err;
[eventDB saveReminder:reminder commit:YES error:&err];
if (err) {
}
}
}];
}
下面是Eventkit的具体介绍
- Eventkit开发包介绍
-
事件提醒开发包(EventKit)由事件库、事件源、日历和事件/提醒组成,他们的关系是:事件库用于直接操作日历数据库,日历数据库中的数据按事件源、日历和事件/提醒三级进行分类组织。每个事件源对应一个准帐户,该帐户下可以有多个日历,日历分两类,一类是用于存储事件的日历,一类是用于存储提醒的日历。这里所说的存储,实际就是分类,反过来的,根据子项对父项进行分类。就如两口缸,一口装水,一口沙子一样,这个缸就是上面提及的日历,水相当于事件,沙子相当于提醒。一户人家的院子里可以摆好多口缸,这个院子就相当于帐户,有两个默认帐户,一个是Local,一个是Other。帐户的类型,还可能有iCloud或Gmail帐号等,一般是邮箱附带的,所以就默认对应着该邮箱地址了。就像 大户人家的总管,管好每户的院子,还有每个院子里的缸一样,事件库直接管理所有的帐户和日历,还有日历下的事件或提醒。管理包括增加、修改、查询、删除(CURD)。
* Eventkit事件库结构
事件库框架授权访问用户的Calendar.app
和Reminders.app
应用的信息。尽管是用两个不同的应用显示用户的日历和提醒数据,但确是同一个框架维护这份数据。同样地,存储这份数据的数据库叫做日历数据库,同时容纳日历和提醒信息。
事件库不但允许你的应用获取用户已经存在的日历及提醒数据,而且它可以让你的应用为任何日历创建新的事件和提醒。另外,事件库让用户可以编辑和删除他们的事件和提醒(整体叫做“日历项”)。更高级的任务,诸如添加闹钟或指定循环事件,也可以使用事件库完成。如果日历数据库有来自你的应用外部的更改发生,事件库可以通过通知监测到,这样你的应用可以做出适当的响应。使用事件库对日历项所做的更改会自动地同步到相关的日历。 - 图1-1事件库结构图
使用Eventkit事件库操作日历
事件(NSCalendar)
-
读写日历事件
你可以使用 EKEventStore 类从用户的日历数据库中获取、创建、编辑和删除事件。你可以获取匹配你提供的谓词的事件自定义的一组事件,或通过唯一标识获取一个单独的事件。你获取到一个事件后,可以使用 EKEvent 类的属性获取访问该事件相关的日历信息。同样的,你可以通过设置 EKEvent 类的属性来修改该事件的日历信息。 -
连接到事件库
在 iOS 6 及以后版本,你必须在事件库初始化后,使用requestAccessToEntityType:completion:
方法请求使用用户的日历数据库。请求访问某个实体类型会异步提示用户允许或禁止你的应用使用他们的日历信息。你应该处理用户授权或禁止你的应用访问权的各种状况:
[store requestAccessToEntityType:EKEntityTypeEvent
completion:^(BOOL granted, NSError *error) {
// handle access here
}];
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
- 注意
EKEventStore 对象需要相对较大量的时间来初始化和释放。因此,你不应该为每一个事件相关的任务都初始化和释放事件库。相反,在你的应用加载时,初始化一个事件库,然后反复地使用这一个来确保连接一直可用。事件库实例不应该在其它事件开发包相对的对象释放前被释放,否则可能发生意想不到的状态。
-
获取事件
有两种方式获取事件。通过谓词或搜索查询获取,会返回零个或多个与给定查询匹配的事件。通过唯一标识获取会返回与给定标识相符的唯一的一个事件。 -
注意
从日历数据库获取事件并不一定按时间顺序返回。要通过日期排序 EKEvent 对象的数组,可以在数组上调用 sortedArrayUsingSelector: 方法,并提供 compareStartDateWithEvent: 方法的选择器。 -
使用谓词
通常是要获得属于某一日期范围的事件。 EKEventStore 的eventsMatchingPredicate:
方法获取属于你提供的谓词中指定的日期范围的所有事件。
注意:尽管eventsMatchingPredicate:
方法接受一个 NSPredicate类型的参数,但你必须提供一个用 EKEventStore 的方法predicateForEventsWithStartDate:endDate:calendars:
创建的谓词。
//以下代码描述了如何获取发生在一天前和当前之后一年之内的所有事件。
// 获取适当的日期(Get the appropriate calendar)
NSCalendar *calendar = [NSCalendar currentCalendar];
// 创建起始日期组件(Create the start date components)
NSDateComponents *oneDayAgoComponents = [[NSDateComponents alloc] init];
oneDayAgoComponents.day = -1;
NSDate *oneDayAgo = [calendar dateByAddingComponents:oneDayAgoComponents
toDate:[NSDate date]
options:0];
// 创建结束日期组件(Create the end date components)
NSDateComponents *oneYearFromNowComponents = [[NSDateComponents alloc] init];
oneYearFromNowComponents.year = 1;
NSDate *oneYearFromNow = [calendar dateByAddingComponents:oneYearFromNowComponents
toDate:[NSDate date]
options:0];
// 用事件库的实例方法创建谓词 (Create the predicate from the event store's instance method)
NSPredicate *predicate = [store predicateForEventsWithStartDate:oneDayAgo
endDate:oneYearFromNow
calendars:nil];
// 获取所有匹配该谓词的事件(Fetch all events that match the predicate)
NSArray *events = [store eventsMatchingPredicate:predicate];
你可以指定一个日历的子集来搜索,这需要传递一个 EKCalendar 对象的数组作为 predicateForEventsWithStartDate:endDate:calendars:
方法的 calendars
参数。你可以从事件库的 calendarsForEntityType:
方法获得用户的不同类型的日历。如果传一个 nil 值,那么就是告诉这个方法获取用户的所有日历。因为方法 eventsMatchingPredicate:
是同步的,而你可能并不想在你的应用主线程中运行它。如果要异步执行的话,那么使用 dispatch_async 函数或使用一个 NSOperation 对象,就可以在另一个线程中运行该方法了。
* 使用唯一标识
如果你之前使用谓词获得了一个事件并知道它的唯一标识,那么你可以使用 EKEventStore 的 eventWithIdentifier:
方法来再次获取该事件。如果它是一个循环事件,那么这个方法就会返回第一次出现的该事件。你可以使用属性 eventIdentifier
获得事件的唯一标识。
-
创建及编辑事件
使用 事件EKEvent 的eventWithEventStore:
方法创建一个新的事件。
你可以通过设置一个新的事件或先前从日历数据库获取的事件的对应属性来编辑事件。你可以编辑的详细内容包括:- 事件的标题用
title
属性 - 事件的起始日期用
startDate
和endDate
属性 - 与事件关联的日历用属性
calendar
- 与事件相关的闹钟用
alarms
属性 (参见 “配置闹钟” 以获得更多详细信息) - 如果一个事件是循环事件,那么它的循环规则用属性 recurrenceRules (参见 “创建循环事件” 以获得更多详细信息)
- 事件的标题用
-
保存和移除事件
如果你的应用修改用户的日历数据库,它必须在这之前先从用户获得确认。应用在未得到用户的特定指示的情况下决不可能修改日历数据库。
你对事件的修改不是持久化的,直到你保存它们为止。使用 EKEventStore 的saveEvent:span:commit:error:
方法保存你的修改到日历数据库中。如果你要从日历数据库移除事件,使用 EKEventStore 的removeEvent:span:commit:error:
方法。无论你保存或移除事件,各自实现的方法都会自动所做的修改到该事件所属于的日历(CalDav、Exchange等等)。
如果你保存一个循环事件,你可以通过给saveEvent:span:commit:error:
方法的参数 span 指定 EKSpanFutureEvents 来使你的更改应用到所有未来出现的该事件中。同样地,你也可以指定removeEvent:span:commit:error:
方法的 span 参数值为EKSpanFutureEvents
来移除一个事件的所有未来的出现。
注意:如果你给 commit 参数传了 NO 值,那么要确保稍侯调用 commit: 方法以持久保存你的更改(译者注:默认传 YES 会立即持久保存更改)。 -
执行批量事件操作
你可以在 EKEventStore 的enumerateEventsMatchingPredicate:usingBlock:
方法执行给定的谓词匹配的所有事件上执行同一个操作。你必须为上述方法使用 EKEventStore 的predicateForEventsWithStartDate:endDate:calendars:
方法创建谓词。你提供的操作是EKEventSearchCallback
类型的块。
typedef void (^EKEventSearchCallback)(EKEvent *event, BOOL *stop);
- 1
- 1
块接收两个参数:
event
:表示当前被操作的事件
stop
:一个布尔值,它决定当前块返回后 enumerateEventsMatchingPredicate:usingBlock: 方法是否应该停止继续处理事件。如果是 YES,那么与该谓词匹配的任何未处理的事件仍保持未处理状态。
注意:使用该方法会引起对用户的日历数据库的有效的修改。确认在你向用户请求批准时,让用户清楚地知道你所要执行的操作。
使用Eventkit事件库操作提醒事项
事件(EKReminder)
-
读写提醒事项
提醒就是一些可以关联到特定时间或位置的任务。他们与日历事件很相似,但可以被标识为完成并且可以不必跨跃一段确切的时间。
因为EKReminder
继承自EKCalendarItem
,所以你可以在提醒上执行与在事件上一样的方法,诸如使用addAlarm:
方法添加一个闹钟,或使用addRecurrenceRule:
方法设置一个循环规则。 -
获取提醒
和事件一样,你必须先建立与事件库的连接,才能访问已存在的提醒。
在 ios 6 及以后的版本中,事件库初始化后,你必须使用requestAccessToEntityType:completion:
请求对用户日历数据库的访问权。请求某一实体类型的访问权会提示用户允许或禁止你的应用使用日历信息。你应该处理用户授权或禁止访问每种情况:
[store requestAccessToEntityType:EKEntityTypeReminder
completion:^(BOOL granted, NSError *error) {
// handle access here
}];
- 搜索
提醒事项
的事件 - 创建及编辑提醒
- 保存和移除提醒
以上三点都和设置日历类似,方法就是将Calendar
替换成Reminder
配置闹钟
闹钟提醒是将一个与前面日历事件或者提醒事项配套的一个闹铃提示。通常一个闹铃事件就是当应用运行时,闹铃作为一种notification来提示用户当前未办的事件(schedules)。如果一个闹铃是被设置在日历事件(calendar event)或者是提醒事件(reminder)中的,这个闹铃通知就会从这个应用发出一个notification。闹铃可以是根据时间设计的,在特定的事件就会响,闹铃也可以是地点提醒的(location-based),当用户到达一个地理围栏geofence(crossing a geofence)就会响。
闹铃事件可以添加到日历中和提醒事项中。
注意:以上说的闹铃并不是一种UILocalNotification服务,而是一种配套日历事件和提醒事项的事件。
- 添加和移除闹铃
你可以通过addAlarm:
方法添加一个闹钟,闹铃可以根据一个确定的事件创建,也可以根据一个时间的推移(offset)来设置启动时间。闹铃创建的时间必须比事件发生的时间早或者同时发生。
你可以通过removeAlarm:
方法移除一个事件的一个闹钟。