iOS系統自帶的Calendar應用很是強大,用戶能夠在其中添加日程事件,而且其提供了接口供其餘應用進行調用,能夠向日歷中進行事件的讀取和寫入。框架
首先,Canlendar應用默認建立了幾個類型的日曆,用戶也能夠根據須要建立自定義的日曆,以下圖:ide
在上圖中,US Holidays、Birthdays、Siri Found in Apps和Calendar是默認建立的幾個日曆,Custom是自定義的日曆,當用戶新建日曆事件時,須要關聯到某個日曆,以下:fetch
對於系統的Reminders,其主要做用是提供事件列表,用戶能夠向事件列表中添加提醒事件,一樣,提供默認建立了兩個事件列表,用戶也能夠根據須要自行建立新的事件列表,以下圖:ui
使用日曆和提醒事宜這兩個應用,能夠提升生活工做效率,例如郵件應用經過與日曆的交互能夠將會議郵件添加到用戶的日程中,EventKit框架則是提供了接口與這兩個應用進行交互。atom
EventKit核心的用途是配合系統的日曆與提醒應用,對事件提醒進行管理,其中核心類即結構以下:url
從圖中能夠看出,重要數據的管理類爲EKEventStore,其餘類都是用來描述對應的數據,下面會一一介紹。spa
第三方應用須要操做用戶的日曆事件,須要獲取用戶受權,首先須要在info.plist文件中添加以下權限請求字段:設計
獲取當前Calendars應用中定義的日曆示例代碼以下:代理
#import "ViewController.h" #import <EventKit/EventKit.h> @interface ViewController () @property (nonatomic, strong) EKEventStore *eventStore; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // 初始化 self.eventStore = [[EKEventStore alloc] init]; // 請求權限 [self.eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError * _Nullable error) { if (!granted) { NSLog(@"拒絕訪問"); } else { // 獲取全部日曆 NSArray *calendars = [self.eventStore calendarsForEntityType:EKEntityTypeEvent]; for (EKCalendar *calendar in calendars) { NSLog(@"%@", calendar); } } }]; } @end
上面方法會獲取到全部的日曆,也能夠經過來源來獲取日曆,後面會具體介紹。code
使用以下代碼能夠添加新的日曆,一般,第三方應用若是要向用戶日曆中添加事件,能夠先添加一個新的日曆:
- (void)createNewCalendar { EKCalendar *calendar = [EKCalendar calendarForEntityType:EKEntityTypeEvent eventStore:self.eventStore]; for (EKSource *source in self.eventStore.sources) { if (source.sourceType == EKSourceTypeLocal) { // 設置來源爲本地 calendar.source = source; } } calendar.title = @"琿少的事項日曆";//自定義日曆標題 calendar.CGColor = [UIColor purpleColor].CGColor;//自定義日曆顏色 NSError* error; [_eventStore saveCalendar:calendar commit:YES error:&error]; }
效果以下圖:
日曆事件的查詢須要構造NSPredicate對象,示例以下:
- (void)queryEvent { for (EKCalendar *cal in [self.eventStore calendarsForEntityType:EKEntityTypeEvent]) { if ([cal.title isEqualToString:@"琿少的事項日曆"]) { NSCalendar *calendar = [NSCalendar currentCalendar]; NSDateComponents *oneMonthFromNowComponents = [[NSDateComponents alloc] init]; oneMonthFromNowComponents.month = 1; NSDate *oneMonthFromNow = [calendar dateByAddingComponents:oneMonthFromNowComponents toDate:[NSDate date] options:0]; // 獲取從今日後一個月的 NSPredicate*predicate = [self.eventStore predicateForEventsWithStartDate:[NSDate date] endDate:oneMonthFromNow calendars:@[cal]]; NSArray *eventArray = [self.eventStore eventsMatchingPredicate:predicate]; NSLog(@"%@", eventArray); } } }
經過存儲的相關接口,也能夠對某個事件進行修改,或建立新的事件,示例以下:
- (void)createEvent { EKCalendar *calen = nil; for (EKCalendar *cal in [self.eventStore calendarsForEntityType:EKEntityTypeEvent]) { if ([cal.title isEqualToString:@"琿少的事項日曆"]) { calen = cal; } } EKEvent *event = [EKEvent eventWithEventStore:self.eventStore]; event.title = @"從應用建立的事件"; event.startDate = [NSDate date]; event.calendar = calen; NSCalendar *calendar = [NSCalendar currentCalendar]; NSDateComponents *oneMonthFromNowComponents = [[NSDateComponents alloc] init]; oneMonthFromNowComponents.hour += 1; NSDate *endDate = [calendar dateByAddingComponents:oneMonthFromNowComponents toDate:[NSDate date] options:0]; event.endDate = endDate; event.notes = @"備註"; [event setAllDay:NO];//設置全天 //保存事件 NSError *error = nil; [self.eventStore saveEvent:event span:EKSpanThisEvent commit:YES error:&error]; NSLog(@"%@",error); }
下面示例代碼用來刪除日曆事件:
- (void)removeEvent { for (EKCalendar *cal in [self.eventStore calendarsForEntityType:EKEntityTypeEvent]) { if ([cal.title isEqualToString:@"琿少的事項日曆"]) { NSCalendar *calendar = [NSCalendar currentCalendar]; NSDateComponents *oneMonthFromNowComponents = [[NSDateComponents alloc] init]; oneMonthFromNowComponents.month = 1; NSDate *oneMonthFromNow = [calendar dateByAddingComponents:oneMonthFromNowComponents toDate:[NSDate date] options:0]; // 獲取從今日後一個月的 NSPredicate*predicate = [self.eventStore predicateForEventsWithStartDate:[NSDate date] endDate:oneMonthFromNow calendars:@[cal]]; NSArray *eventArray = [self.eventStore eventsMatchingPredicate:predicate]; [self.eventStore removeEvent:eventArray.firstObject span:EKSpanThisEvent error:nil]; } } }
提醒事件的用法和日曆事件的用法基本一致,首先在Reminder應用中,每個列表就是一個日曆,下面代碼示例了向列表中插入提醒事件的方法:
- (void)createNewReminder { EKReminder *reminder = [EKReminder reminderWithEventStore:self.eventStore]; reminder.title = @"從應用建立的事件"; reminder.notes = @"備註"; for (EKCalendar *cal in [self.eventStore calendarsForEntityType:EKEntityTypeReminder]) { if ([cal.title isEqualToString:@"自定義"]) { reminder.calendar = cal; } } //保存事件 NSError *error = nil; [self.eventStore saveReminder:reminder commit:YES error:&error]; NSLog(@"%@", error); }
一樣,對應的也有Reminder的查詢,刪除等操做。
EKEventStore類是EventKit中的核心類,用來對日曆和提醒的事件進行操做。其中方法與屬性列舉以下:
@interface EKEventStore : NSObject // 獲取用戶受權狀況 + (EKAuthorizationStatus)authorizationStatusForEntityType:(EKEntityType)entityType; // 初始化方法 - (id)init; // 使用指定的來源建立實例對象 - (instancetype)initWithSources:(NSArray<EKSource *> *)sources; // 請求某個類型的事件訪問權限 - (void)requestAccessToEntityType:(EKEntityType)entityType completion:(EKEventStoreRequestAccessCompletionHandler)completion; // 惟一標識 @property(nonatomic, readonly) NSString *eventStoreIdentifier; // 代理源 使用initWithSources方法實例化對應 @property (nonatomic, readonly) NSArray<EKSource *> *delegateSources; // 可用的來源 @property (nonatomic, readonly) NSArray<EKSource *> *sources; // 經過identifier獲取指定的來源對象 - (nullable EKSource *)sourceWithIdentifier:(NSString *)identifier; // 經過類型來獲取全部的日曆對象 - (NSArray<EKCalendar *> *)calendarsForEntityType:(EKEntityType)entityType; // 獲取默認的日曆對象 @property(nullable, nonatomic, readonly) EKCalendar *defaultCalendarForNewEvents; - (nullable EKCalendar *)defaultCalendarForNewReminders; // 經過identifier獲取日曆對象 - (nullable EKCalendar *)calendarWithIdentifier:(NSString *)identifier; // 添加一個新的日曆,commit參數是都是否馬上提交修改 - (BOOL)saveCalendar:(EKCalendar *)calendar commit:(BOOL)commit error:(NSError **)error; // 刪除某個日曆 - (BOOL)removeCalendar:(EKCalendar *)calendar commit:(BOOL)commit error:(NSError **)error; // 根據identifier獲取日程對象 - (nullable EKCalendarItem *)calendarItemWithIdentifier:(NSString *)identifier; // 獲取一組日程對象 - (NSArray<EKCalendarItem *> *)calendarItemsWithExternalIdentifier:(NSString *)externalIdentifier; // 新建事件 span用來設置對於週期事件本次設置是否影響將來的事件 - (BOOL)saveEvent:(EKEvent *)event span:(EKSpan)span error:(NSError **)error; - (BOOL)saveEvent:(EKEvent *)event span:(EKSpan)span commit:(BOOL)commit error:(NSError **)error; // 移出事件 - (BOOL)removeEvent:(EKEvent *)event span:(EKSpan)span error:(NSError **)error; - (BOOL)removeEvent:(EKEvent *)event span:(EKSpan)span commit:(BOOL)commit error:(NSError **)error; // 經過identifier獲取事件 - (nullable EKEvent *)eventWithIdentifier:(NSString *)identifier; // 使用給定的NSPredicate進行事件查詢 - (NSArray<EKEvent *> *)eventsMatchingPredicate:(NSPredicate *)predicate; // 使用給定的NSPredicate進行事件枚舉 - (void)enumerateEventsMatchingPredicate:(NSPredicate *)predicate usingBlock:(EKEventSearchCallback)block; // 構造NSPredicate查詢對象 - (NSPredicate *)predicateForEventsWithStartDate:(NSDate *)startDate endDate:(NSDate *)endDate calendars:(nullable NSArray<EKCalendar *> *)calendars; // 提醒相關 // 新建一條提醒 - (BOOL)saveReminder:(EKReminder *)reminder commit:(BOOL)commit error:(NSError **)error; // 刪除提醒 - (BOOL)removeReminder:(EKReminder *)reminder commit:(BOOL)commit error:(NSError **)error; // 進行提醒查詢 - (id)fetchRemindersMatchingPredicate:(NSPredicate *)predicate completion:(void (^)(NSArray<EKReminder *> * __nullable reminders))completion; // 取消某此查詢 - (void)cancelFetchRequest:(id)fetchIdentifier; // 構造查詢對象 - (NSPredicate *)predicateForRemindersInCalendars:(nullable NSArray<EKCalendar *> *)calendars; - (NSPredicate *)predicateForIncompleteRemindersWithDueDateStarting:(nullable NSDate *)startDate ending:(nullable NSDate *)endDate calendars:(nullable NSArray<EKCalendar *> *)calendars; - (NSPredicate *)predicateForCompletedRemindersWithCompletionDateStarting:(nullable NSDate *)startDate ending:(nullable NSDate *)endDate calendars:(nullable NSArray<EKCalendar *> *)calendars; // 將全部的修改一次性進行提交 - (BOOL)commit:(NSError **)error; // 重置全部的修改 - (void)reset; // 刷新來源 - (void)refreshSourcesIfNecessary; @end
EKEntityType用來描述類型,定義以下:
typedef NS_ENUM(NSUInteger, EKEntityType) { EKEntityTypeEvent, // 日曆類型 EKEntityTypeReminder // 提醒類型 };
EKAuthorizationStatus用來描述用戶受權狀態:
typedef NS_ENUM(NSInteger, EKAuthorizationStatus) { EKAuthorizationStatusNotDetermined = 0, // 用戶還沒有選擇 EKAuthorizationStatusRestricted, // 應用無權訪問 EKAuthorizationStatusDenied, // 用戶拒絕受權 EKAuthorizationStatusAuthorized, // 用戶受權 } ;
EKSpan用來描述所影響事件類型:
typedef NS_ENUM(NSInteger, EKSpan) { EKSpanThisEvent, // 隻影響本次事件 EKSpanFutureEvents // 對將來的事件也會產生影響 };
首先,EKSource描述了,例如某些日曆是系統默認建立的,用戶沒有權限進行修改或刪除,某些日曆是用戶自定義建立的,還有些可能來自雲端,這個類繼承自EKObject類,首先先看EKObject類的定義:
@interface EKObject : NSObject // 數據對象是否有修改 @property (nonatomic, readonly) BOOL hasChanges; // 是不是一個新的數據對象(爲存儲到日曆或提醒中) - (BOOL)isNew; // 重置數據 將全部屬性清空 - (void)reset; // 回滾未提交的操做 - (void)rollback; // 刷新數據 - (BOOL)refresh; @end
EKSource是EKObject的子類,其中封裝屬性和方法以下:
@interface EKSource : EKObject // 來源對象的ID 惟一標識 @property(nonatomic, readonly) NSString *sourceIdentifier; // 來源對象的類型 @property(nonatomic, readonly) EKSourceType sourceType; // 來源對象的標題 @property(nonatomic, readonly) NSString *title; // 此來源對象中所包含的日曆對象 @property(nonatomic, readonly) NSSet<EKCalendar *> *calendars; // 獲取此來源對象中某個類型的日曆 (日曆或提醒) - (NSSet<EKCalendar *> *)calendarsForEntityType:(EKEntityType)entityType; @end
EKSourceType枚舉以下:
typedef NS_ENUM(NSInteger, EKSourceType) { EKSourceTypeLocal, // 本地 EKSourceTypeExchange, // 交換 EKSourceTypeCalDAV, // CalDAV或iCloud EKSourceTypeMobileMe, // MobileMe EKSourceTypeSubscribed,// 訂閱 EKSourceTypeBirthdays // 生日 };
EKCalendar是具體的日曆對象,開發者能夠對自定義的日曆進行讀寫操做,其也是繼承自EKObject對象,解析以下:
@interface EKCalendar : EKObject // 經過制定的EKEventStore對象建立一個新的日曆對象 + (EKCalendar*)calendarWithEventStore:(EKEventStore *)eventStore; // 建立某個類型的日曆對象 (日曆或提醒) + (EKCalendar *)calendarForEntityType:(EKEntityType)entityType eventStore:(EKEventStore *)eventStore; // 當前日曆對象所屬於的來源 @property(null_unspecified, nonatomic, strong) EKSource *source; // 日曆對象的ID @property(nonatomic, readonly) NSString *calendarIdentifier; // 日曆對象的標題 會在 日曆 應用 或 提醒 應用中顯示 @property(nonatomic, copy) NSString *title; // 當前日曆對象的類型 /* typedef NS_ENUM(NSInteger, EKCalendarType) { EKCalendarTypeLocal, EKCalendarTypeCalDAV, EKCalendarTypeExchange, EKCalendarTypeSubscription, EKCalendarTypeBirthday }; */ @property(nonatomic, readonly) EKCalendarType type; // 當前日曆是否支持編輯 @property(nonatomic, readonly) BOOL allowsContentModifications; // 當前日曆是否爲訂閱類型 @property(nonatomic, readonly, getter=isSubscribed) BOOL subscribed; // 當前日曆對象是否可變 @property(nonatomic, readonly, getter=isImmutable) BOOL immutable; // 對應的提示顏色 @property(null_unspecified, nonatomic) CGColorRef CGColor; // 支持的事件狀態 /* typedef NS_OPTIONS(NSUInteger, EKCalendarEventAvailabilityMask) { EKCalendarEventAvailabilityNone = 0, // calendar doesn't support event availability EKCalendarEventAvailabilityBusy = (1 << 0), EKCalendarEventAvailabilityFree = (1 << 1), EKCalendarEventAvailabilityTentative = (1 << 2), EKCalendarEventAvailabilityUnavailable = (1 << 3), }; */ @property(nonatomic, readonly) EKCalendarEventAvailabilityMask supportedEventAvailabilities; // 支持的日曆類型 /* typedef NS_OPTIONS(NSUInteger, EKEntityMask) { EKEntityMaskEvent = (1 << EKEntityTypeEvent), EKEntityMaskReminder = (1 << EKEntityTypeReminder) }; */ @property(nonatomic, readonly) EKEntityMask allowedEntityTypes; @end
EKEvent與EKReminder類在設計上是平行的兩個類,前面的示例代碼中也有介紹。EKEvent對應系統日曆中的事件,EKReminder對應系統提醒應用中的事件。他們都繼承自EKCalendarItem類,EKCalendarItem也是EKObject的子類,這個類解析以下:
@interface EKCalendarItem : EKObject // UUID 事件的惟一標識 @property(nonatomic, readonly) NSString *UUID; // 事件所屬於的日曆 @property(nonatomic, strong, null_unspecified) EKCalendar *calendar; // 事件的ID @property(nonatomic, readonly) NSString *calendarItemIdentifier; // 事件的標題 @property(nonatomic, copy, null_unspecified) NSString *title; // 地理位置 @property(nonatomic, copy, nullable) NSString *location; // 備註信息 @property(nonatomic, copy, nullable) NSString *notes; // 網址URL @property(nonatomic, copy, nullable) NSURL *URL; // 最後一次更改時間 @property(nonatomic, readonly, nullable) NSDate *lastModifiedDate; // 事件建立的時間 @property(nonatomic, readonly, nullable, strong) NSDate *creationDate; // 時區 @property(nonatomic, copy, nullable) NSTimeZone *timeZone; // 是否有鬧鐘提醒 @property(nonatomic, readonly) BOOL hasAlarms; // 是否包含重複提醒的規則 @property(nonatomic, readonly) BOOL hasRecurrenceRules; // 是否包含參會人信息 @property(nonatomic, readonly) BOOL hasAttendees; // 是否包含備註 @property(nonatomic, readonly) BOOL hasNotes; // 參會人信息 @property(nonatomic, readonly, nullable) NSArray<__kindof EKParticipant *> *attendees; // 鬧鐘提醒信息 @property(nonatomic, copy, nullable) NSArray<EKAlarm *> *alarms; // 添加鬧鐘規則 - (void)addAlarm:(EKAlarm *)alarm; // 刪除鬧鐘規則 - (void)removeAlarm:(EKAlarm *)alarm; // 包含的按期規則 @property(nonatomic, copy, nullable) NSArray<EKRecurrenceRule *> *recurrenceRules; // 添加按期規則 - (void)addRecurrenceRule:(EKRecurrenceRule *)rule; // 刪除按期規則 - (void)removeRecurrenceRule:(EKRecurrenceRule *)rule; @end
上面類中有引用到一些其餘的核心類,如EKParticipant、EKAlarm和EKRecurrenceRule,後面會介紹。
EKEvent是EKCalendarItem的子類,其對應日曆應用中的事件,解析以下:
@interface EKEvent : EKCalendarItem // 建立一個新的事件對象 + (EKEvent *)eventWithEventStore:(EKEventStore *)eventStore; // 系統分類的事件ID @property(null_unspecified, nonatomic, readonly) NSString *eventIdentifier; // 是不是一個全天事件 @property(nonatomic, getter=isAllDay) BOOL allDay; // 事件開始日期 @property(null_unspecified, nonatomic, copy) NSDate *startDate; // 事件結束日期 @property(null_unspecified, nonatomic, copy) NSDate *endDate; // 觸發型事件 到達某個地理位置後觸發 @property(nonatomic, copy, nullable) EKStructuredLocation *structuredLocation; // 與另外一個事件的開始事件進行比較 - (NSComparisonResult)compareStartDateWithEvent:(EKEvent *)other; // 事件的組織者 @property(nonatomic, readonly, nullable) EKParticipant *organizer; // 事件行爲狀態 @property(nonatomic) EKEventAvailability availability; // 事件確認狀態 /* typedef NS_ENUM(NSInteger, EKEventStatus) { EKEventStatusNone = 0, EKEventStatusConfirmed, EKEventStatusTentative, EKEventStatusCanceled, }; */ @property(nonatomic, readonly) EKEventStatus status; // 事件是不是獨立的 @property(nonatomic, readonly) BOOL isDetached; // 事件發生時間 @property(null_unspecified, nonatomic, readonly) NSDate *occurrenceDate; // 刷新 - (BOOL)refresh; // 對於生日事件 對應的生日聯繫人ID @property(nonatomic, readonly, nullable) NSString *birthdayContactIdentifier; @end
EKReminder與EKEvent相似,其對應系統的提醒事宜應用中的事件:
@interface EKReminder : EKCalendarItem // 新建提醒事件 + (EKReminder *)reminderWithEventStore:(EKEventStore *)eventStore; // 開始時間 @property(nonatomic, copy, nullable) NSDateComponents *startDateComponents; // 發生時間 @property(nonatomic, copy, nullable) NSDateComponents *dueDateComponents; // 是否已完成 @property(nonatomic, getter=isCompleted) BOOL completed; // 完成時間 @property(nonatomic, copy, nullable) NSDate *completionDate; // 優先級 1-4 爲高 5 爲中 6-9爲低 @property(nonatomic) NSUInteger priority; @end
EKAlarm對應鬧鐘對象,事件能夠綁定鬧鐘提醒,解析以下:
@interface EKAlarm : EKObject // 經過絕對時間建立鬧鐘 + (EKAlarm *)alarmWithAbsoluteDate:(NSDate *)date; // 經過與如今的相對事件建立鬧鐘 + (EKAlarm *)alarmWithRelativeOffset:(NSTimeInterval)offset; // 與如今的相對時間 @property(nonatomic) NSTimeInterval relativeOffset; // 絕對時間 @property(nonatomic, copy, nullable) NSDate *absoluteDate; // 地理位置觸發的鬧鐘,對應的地理位置 @property(nonatomic, copy, nullable) EKStructuredLocation *structuredLocation; // 觸發方式 /* typedef NS_ENUM(NSInteger, EKAlarmProximity) { EKAlarmProximityNone, EKAlarmProximityEnter, // 進入觸發條件 EKAlarmProximityLeave // 離開觸發條件 }; */ @property(nonatomic) EKAlarmProximity proximity; // 鬧鐘類型 /* typedef NS_ENUM(NSInteger, EKAlarmType) { EKAlarmTypeDisplay, EKAlarmTypeAudio, EKAlarmTypeProcedure, EKAlarmTypeEmail }; */ @property(nonatomic, readonly) EKAlarmType type; // 提醒的郵件地址 @property(nonatomic, copy, nullable) NSString *emailAddress; // 提醒的音效名稱 @property(nonatomic, copy, nullable) NSString *soundName; // 提醒後打開的網頁 @property(nonatomic, copy, nullable) NSURL *url; @end
EKStructuredLocation用來描述地區區域結構,以下:
@interface EKStructuredLocation : EKObject // 經過標題建立 + (instancetype)locationWithTitle:(NSString *)title; // 經過地圖點建立 + (instancetype)locationWithMapItem:(MKMapItem *)mapItem NS_AVAILABLE(10_11, 9_0); // 標題 @property(nullable, nonatomic, strong) NSString *title; // 經緯度地理信息 @property(nonatomic, strong, nullable) CLLocation *geoLocation; // 半徑 @property(nonatomic) double radius; @end
EKParticipant類是與事件相關的聯繫人數據類,解析以下:
@interface EKParticipant : EKObject // 網址 @property(nonatomic, readonly) NSURL *URL; // 姓名 @property(nonatomic, readonly, nullable) NSString *name; // 狀態 /* typedef NS_ENUM(NSInteger, EKParticipantStatus) { EKParticipantStatusUnknown, // 未知 EKParticipantStatusPending, // 等待處理 EKParticipantStatusAccepted, // 接收 EKParticipantStatusDeclined, // 拒絕 EKParticipantStatusTentative,// 嘗試 EKParticipantStatusDelegated,// 代理 EKParticipantStatusCompleted,// 完成 EKParticipantStatusInProcess // 進行中 }; */ @property(nonatomic, readonly) EKParticipantStatus participantStatus; // 人員的角色 /* typedef NS_ENUM(NSInteger, EKParticipantRole) { EKParticipantRoleUnknown, // 未知 EKParticipantRoleRequired, // 必要的 EKParticipantRoleOptional, // 可選參與的 EKParticipantRoleChair, // 主持人 EKParticipantRoleNonParticipant // 參與者 }; */ @property(nonatomic, readonly) EKParticipantRole participantRole; // 參與人類型 /* typedef NS_ENUM(NSInteger, EKParticipantType) { EKParticipantTypeUnknown, EKParticipantTypePerson, EKParticipantTypeRoom, EKParticipantTypeResource, EKParticipantTypeGroup }; */ @property(nonatomic, readonly) EKParticipantType participantType; // 是不是當前用戶 @property(nonatomic, readonly, getter=isCurrentUser) BOOL currentUser; // 用來查找聯繫人的NSPredicate對象 @property(nonatomic, readonly) NSPredicate *contactPredicate;