使用 EventKit 向系統日曆中新增事件

秋刀生魚片發表於2017-06-05

使用 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 = U65e5U5386; type = CalDAV; allowsModify = YES; color = #1BADF8;}”,
“EKCalendar <0x1740a5640> {title = U5de5U4f5c; type = CalDAV; allowsModify = YES; color = #63DA38;}”,
“EKCalendar <0x1740a55e0> {title = U5bb6U5ead; 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 coobjectivecit: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-objectivec-dd-HH-objectivec"];
        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的基礎教程。

相關文章