使用 EventKit 向系统日历中添加事件

使用 EventKit 向系统日历中添加事件

本文主要内容是如何一步一步使用EventKit在iOS设备中添加日历,并在日历中添加事件和提醒事项。

Github源码

类和属性

EKAlarm 提醒操作类

EKAlarm类用于提供操作系统日历通知的相关接口,通知时间既可以是绝对时间,也可以是相对时间。

实例化方法

//绝对时间
+ (EKAlarm *)alarmWithAbsoluteDate:(NSDate *)date;
//相对时间
+ (EKAlarm *)alarmWithRelativeOffset:(NSTimeInterval)offset;

属性相关类

EKStructuredLocation 通知的位置属性,包括标题title,地理位置geoLocation和半径radius

EKAlarmProximity 是一个标记为进入或者离开的枚举类型

typedef NS_ENUM(NSInteger, EKAlarmProximity) {
    EKAlarmProximityNone,
    EKAlarmProximityEnter, //进入
    EKAlarmProximityLeave  //离开
};

EKAlarmType 记录通知类型的枚举属性

typedef NS_ENUM(NSInteger, EKAlarmType) {
    EKAlarmTypeDisplay,     //展示信息
    EKAlarmTypeAudio,       //播放声音
    EKAlarmTypeProcedure,   //打开网址
    EKAlarmTypeEmail        //发邮件
};

EKEventStore 事件管理类

首先,通过``可以进行系统授权,使用下面的方法

//申请权限
- (void)requestAccessToEntityType:(EKEntityType)entityType completion:(EKEventStoreRequestAccessCompletionHandler)completion NS_AVAILABLE(10_9, 6_0);

//获取当前权限
+ (EKAuthorizationStatus)authorizationStatusForEntityType:(EKEntityType)entityType NS_AVAILABLE(10_9, 6_0);

枚举量包括 实例类型EKEntityType 和 实例蒙版EKEntityMask

EKCalendar 日历操作类

EKCalendarEKEventStore 的关系可以理解为,一个EKEventStore可以包含多个EKCalendar

这个类的类型枚举变量是

typedef NS_ENUM(NSInteger, EKCalendarType) {
   EKCalendarTypeLocal,
   EKCalendarTypeCalDAV,
   EKCalendarTypeExchange,
   EKCalendarTypeSubscription,
   EKCalendarTypeBirthday
};

其中,EKCalendarTypeCalDAVEKCalendarTypeExchange 是两种邮箱账户的事件同步类型,EKCalendarTypeBirthday是一个内置的生日日历,EKCalendarTypeLocal和设备同步,EKCalendarTypeSubscription则是用于本地的同步类型。

EKEvent 事件操作类 与 EKReminder 提醒操作类

EKEventEKReminder一样继承于EKCalendarItem

业务代码

上面对于头文件的分析,有助于我们实际编写代码。

要点一 添加授权描述

首先需要在项目的plist文件中,加入申请系统日历使用的描述,否则无法发起授权请求。键为NSCalendarsUsageDescription,值为提交请求的描述。

然后判断当前的授权状态

+ (BOOL)accessForEventKit:(EKEntityType)type
{
    return [EKEventStore authorizationStatusForEntityType:type] == EKAuthorizationStatusAuthorized;
}

当然,可以增加额外的判断,比如当状态为EKAuthorizationStatusDenied的时候,可以提醒用户前往系统设置打开系统日历的授权,我们上面这段代码只是简单的判断是否拥有系统日历的操作权限,当拥有权限时,进行业务代码,如果还没有进行过授权,则用下满的方法吊起授权

+ (void)accessForEventKitType:(EKEntityType)type result:(void(^)(BOOL))result
{
    EKEventStore* store = [[EKEventStore alloc] init];
    [store requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError * _Nullable error) {
        if (!granted) {
            NSLog(@"%@",error);
        }
        if (result) {
            result(granted);
        }
    }];
}

这里是否封装成方法都可以,我这里写成类方法是为了方便读者在任意位置粘贴代码和调用。

授权完成以后就可以进行业务代码的操作了。

首先使用下面的代码获取系统日历中全部的日历

+ (NSArray*)calendarWithType:(EKEntityType)type
{
    EKEventStore* store = [[EKEventStore alloc] init];
    return [store calendarsForEntityType:type];
}

这里同样区分事件的种类,获得结果如下,每个苹果账号的日历内容不尽相同

(
    "EKCalendar <0x1740a5580> {title = Birthdays; type = Birthday; allowsModify = NO; color = #8295AF;}",
    "EKCalendar <0x1740a5dc0> {title = Calendar; type = CalDAV; allowsModify = YES; color = #1BADF8;}",
    "EKCalendar <0x1740a56a0> {title = \U65e5\U5386; type = CalDAV; allowsModify = YES; color = #1BADF8;}",
    "EKCalendar <0x1740a5640> {title = \U5de5\U4f5c; type = CalDAV; allowsModify = YES; color = #63DA38;}",
    "EKCalendar <0x1740a55e0> {title = \U5bb6\U5ead; type = CalDAV; allowsModify = YES; color = #FFCC00;}"
)

EKCalendarEKCalendarItem 并不是双向的可读取关系,通过EKCalendarItem实例可以获取它的EKCalendar,但我们无法通过EKCalendar获取系统全部的EKCalendarItem,这样不同的应用之间是无法相互操作事件的。

每一个EKCalendarItem拥有独一无二的calendarItemIdentifier标识,是每个事件的id,这个字符串是应用自己分配的,保证了每个应用可以在添加了事件以后,针对性的进行事件修改。

大家注意到上面输出的EKCalendar中有一个日历是禁止应用修改的,就是Birthdays日历,因为这个日历是同步于通讯录中的联系人生日的特殊日历。

EKCalendar除了上面获取的系统已有日历,我们也可以添加自己定义的日历

要点二 日历的calendarIdentifier存储

应用需要自己存储自己添加的日历的唯一标识,就是calendarIdentifier,我们这里使用NSUserDefault来存储。

+ (void)createCalendar
{
    NSUserDefaults * def = [NSUserDefaults standardUserDefaults];
    NSString* calendarIdentifier = [def valueForKey:@"testCalendarIdentifier"];
    EKCalendar* birthday = [store calendarWithIdentifier:calendarIdentifier];
    //这里如果calendarIdentifier为nil,则EKCalendar也会为nil
    if (!birthday) {
            birthday = [EKCalendar calendarForEntityType:EKEntityTypeEvent eventStore:store];
            birthday.title = @"test日历";
            //注意calendarIdentifier自动生成的,这里要保存下来
            [def setObject:birthday.calendarIdentifier forKey:@"testCalendarIdentifier"]; 
            [def synchronize];
            NSError* error;
            [store saveCalendar:birthday commit:YES error:&error];
            if (error) {
                NSLog(@"%@",error);
            }
   }
}

上面的代码执行将无法添加日历,需要越过两个坑

要点三 坑

坑一是EKCalendar需要设置EKSource才可以添加,所以会得到下面的错误

Error Domain=EKErrorDomain Code=14 "Calendar has no source" UserInfo={NSLocalizedDescription=Calendar has no source}

然而EKSource并不可以通过EventKit管理,也就是说,系统有哪些日历账户,就只能用哪些,获取的办法如下:

+ (EKSource*)sourceWithType:(EKSourceType)type
{
    EKEventStore* store = [[EKEventStore alloc] init];
    EKSource *localSource = nil;
    NSLog(@"%@",store.sources);
    for (EKSource *source in store.sources)
    {
        if (source.sourceType == type)
        {
            localSource = source;
            break;
        }
    }
    return localSource;
}

我们通过遍历所有的系统Source,来匹配我们需要的Source。

这里就引出坑二,当系统开启或关闭了iCloud日历功能的时候,Source会有不同,例如开启iCloud日历的时候,我的设备只有一个EKSourceTypeCalDAV类型的叫iCloud的Source和一个Other类型的Source(每个设备可能不尽相同),但在其他没有开启的设备上,才有EKSourceTypeLocal类型的Source,所以上面一段代码的Source匹配,可能要执行多次,或者按照先后顺序匹配,如果单独只匹配一种类型,会有可能找不到可以使用的Source。

找到Source以后,将其赋值给Calendar,注意EKCalendar的这个属性虽然不是Readonly,但只能在初始化日历的时候进行设置,不能再更改。

birthday.source = [[self class] sourceWithType:EKSourceTypeCalDAV];
if (!birthday.source) {
    birthday.source = [[self class] sourceWithType:EKSourceTypeLocal];
}

如此则可顺利添加自定义的日历。

最后一步添加事件则很简单

+ (void)addEvent
{
         EKEvent* event = [EKEvent eventWithEventStore:store];
        event.calendar = birthday;
        event.title = @"我的生日";
        NSDateFormatter* formatter = [[NSDateFormatter alloc] init];
        [formatter setDateFormat:@"yyyy-MM-dd-HH-mm"];
        event.startDate = [formatter dateFromString:@"2017-11-12-00-00"];
        event.endDate = [formatter dateFromString:@"2017-11-12-23-59"];
        NSError* error;
        [store saveEvent:event span:EKSpanThisEvent error:&error];
        if (error) {
            NSLog(@"%@",error);
        }
        NSLog(@"%@",event.eventIdentifier);
} 

event设置标题、日历和起始日期即可,保存步骤中用到的EKSpanThisEvent枚举表示只应用于当前事件,这个枚举的另一个值EKSpanFutureEvents表示应用到所有此事件,包括重复事件的未来的事件。

最后保存成功的话,得到的eventIdentifier,应用可以根据需要保存在本地。

以上就是EventKit的基础教程。

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

推荐阅读更多精彩内容