最近在做日歷同步的需求,趁著已經(jīng)提測,整理一些坑坑洼洼的地方。
和產(chǎn)品經(jīng)理一起研究了一下市面上該功能的實(shí)現(xiàn),絕大多數(shù)都是把本地手機(jī)日歷的日程單向同步到自己app中去。而我們產(chǎn)品經(jīng)理反其道而行,同步依然是單向的,但是是要從app的日程業(yè)務(wù)中把數(shù)據(jù)同步到日歷中去。其實(shí)這樣的需求對于我們本身的業(yè)務(wù)來講是說得通的,而且不需要通過push消息就能讓用戶自己去定義提醒的時(shí)間。但是在預(yù)研階段就發(fā)現(xiàn)了問題,所以正式開發(fā)以前,給她們總結(jié)了一些解決思路和不可避免的問題。
問題一:EKEvent對象的eventIdentifier屬性是只讀的
@property(null_unspecified, nonatomic, readonly) NSString *eventIdentifier;
看到readonly我瞬間明白了其他APP為什么是手機(jī)本地日歷單向同步到應(yīng)用中,因?yàn)闃I(yè)務(wù)對象的唯一標(biāo)識不能和本地的日歷建立有效的連接。這樣就會(huì)導(dǎo)致如果我們對APP內(nèi)日程的數(shù)據(jù)進(jìn)行的更改或者刪除操作,本地日歷中的日程就沒有辦法同步更新,因?yàn)槠ヅ洳簧蟸
解決方案:將我們的日程id帶入到本地日程中去。
eventIdentifier用不了了,只能找其他屬性,并且是string類型,最后決定用url,當(dāng)然做了一些其他處理。不過問題依然是有的,比如該屬性是暴露給用戶的,用戶可以自己去編輯。那么就會(huì)導(dǎo)致有新的編輯過的數(shù)據(jù)過來以后,我只能把該日程處理成新增日程。
問題二:獲取本地日歷中的日程數(shù)據(jù)數(shù)據(jù)量可能會(huì)很大,導(dǎo)致與服務(wù)端返回的新數(shù)據(jù)進(jìn)行匹配的時(shí)候雙重for循環(huán)影響效率(雖然用戶感知不到)
解決方案:使用allowsContentModifications屬性
-(NSMutableArray*)getLocalSchedules{
? ? NSMutableArray *allowsModifyEvents = [NSMutableArray array];
? ? NSDate *startDate = startdate;
? ? NSDate *endDate = enddate;
? ? NSPredicate *pre = [self.store predicateForEventsWithStartDate:startDate endDate:endDate calendars:nil];
? ? NSArray *events = [self.store eventsMatchingPredicate:pre];
? ? events = [eventssortedArrayUsingSelector:@selector(compareStartDateWithEvent:)];
? ? for(EKEvent*eventinevents) {
? ? ? ? if (event.calendar.allowsContentModifications == YES) {
? ? ? ? ? ? [allowsModifyEventsaddObject:event];
? ? ? ? }
? ? }
? ? returnallowsModifyEvents;
}
是的,由于我們手動(dòng)添加的數(shù)據(jù)都是可以手動(dòng)編輯的,所以event的allowsContentModifications這一只讀屬性剛好可以用到。可以減少很多系統(tǒng)日歷自帶的event對象,比如節(jié)假日、節(jié)氣等等。
問題三:日程需要分賬戶
解決方案:使用EKCalendar
//為日程添加日歷分類
EKSource*myLocalSource =nil;
EKCalendar*myLocalCalendar;
NSArray *calendars = [self.store calendarsForEntityType:EKEntityTypeEvent];
NSString*calendarTitle = [NSStringstringWithFormat:@"%@",userName];//這里使用username是因?yàn)槲覀兊腁PP可以進(jìn)行用戶切換,產(chǎn)品經(jīng)理希望不同用戶的日程保存到不同的分類下。但是由于EKCalendar的calendarIdentifier屬性也是只讀的,所以目前只能用username進(jìn)行本地和服務(wù)端返回?cái)?shù)據(jù)的匹配。
//日歷優(yōu)先取本地已有的
for(EKCalendar*calendar in calendars) {
? ? ? ? if([calendar.title isEqualToString: calendarTitle]) {
? ? ? ? ? ? myLocalCalendar = calendar;
? ? ? ? ? ? break;
? ? ? ? }
}
? ? //本地沒有則新建
? ? if(myLocalCalendar ==nil) {
? ? ? ? //先取已經(jīng)存在本地的個(gè)人source
? ? ? ? for(EKSource*calendarSource in self.store.sources) {
? ? ? ? ? ? if(calendarSource.sourceType==EKSourceTypeLocal) {
? ? ? ? ? ? ? ? myLocalSource = calendarSource;
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? //EKSource的類型有很多種,Local類型在用戶打開日歷的cloud同步以后會(huì)變成CalDAV類型
? ? ? ? if(myLocalSource ==nil) {
? ? ? ? ? ? for(EKSource*calendarSource in self.store.sources) {
? ? ? ? ? ? ? ? if(calendarSource.sourceType==EKSourceTypeCalDAV&&
? ? ? ? ? ? ? ? ? ? [calendarSource.titleisEqualToString:@"iCloud"]) {//該判斷條件不知道有沒有更好的方案,也是在網(wǎng)上找到的。
? ? ? ? ? ? ? ? ? ? myLocalSource = calendarSource;
? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? myLocalCalendar = [EKCalendar calendarForEntityType:EKEntityTypeEvent eventStore:self.store];
? ? ? ? myLocalCalendar.title= calendarTitle;
? ? ? ? CGColorSpaceRef rgbSapceRef = CGColorSpaceCreateDeviceRGB();
? ? ? ? CGFloatrgbComponents[] = {1,0,0,1};// RGBA 顏色組件
? ? ? ? CGColorRefrgbColorRef =CGColorCreate(rgbSapceRef, rgbComponents);
? ? ? ? myLocalCalendar.CGColor= rgbColorRef;
? ? ? ? myLocalCalendar.source= myLocalSource;
? ? ? ? NSError*err;
? ? ? ? [self.store saveCalendar:myLocalCalendar commit:YES error:&err];
? ? ? ? if (err) {//新建日歷失敗的話則將日程的日歷分類到默認(rèn)日歷下
? ? ? ? ? ? [newEventsetCalendar:[self.store defaultCalendarForNewEvents]];
? ? ? ? }else{
? ? ? ? ? ? [newEventsetCalendar:myLocalCalendar];
? ? ? ? }
? ? }else{
? ? ? ? [newEventsetCalendar:myLocalCalendar];
? ? }
結(jié)論:EventKit框架中有太多的只讀屬性的對象,其實(shí)正確的做法是把已經(jīng)存到本地的EKEvent對象的eventIdentifier屬性返回給我們自己的服務(wù)器,讓后臺與業(yè)務(wù)日程進(jìn)行關(guān)聯(lián)。但是目前該方案由于種種原因沒有最終拍死,所以只能原生負(fù)責(zé)第一期的需求先實(shí)現(xiàn)。后面再慢慢埋坑吧~基本上一些重要的代碼也就上面一點(diǎn)點(diǎn),就不上demo了。其實(shí)我蠻喜歡做這樣的需求的,沒有UI\UE。后臺或者前端返回來數(shù)據(jù)我就處理數(shù)據(jù)就好了。不到一天時(shí)間就能搞定,不過前期一定要做好預(yù)研工作,把問題盡快的暴露給項(xiàng)目組,然后大家一起討論解決方案,后面才能水到渠成。