iOS日历日程大全

最近公司有一个日程同步日历的功能,以前没有做过,网上查资料,反反复复搞的时间还不短,算踩了不少坑;下面把一些重要的注意点与大家分享,希望有同学做此类型的功能可以少走弯路;

下面正式进入正题!!👇

本次分享主要有如下几点内容:

  1. 单条日程的写入;
  2. 重复日程的写入;
  3. 全天日程的写入;
  4. 系统日历中自定义的日程读取及删除;
  5. ⚠️注意的地方⚠️

一、单条日程的写入

说到做日程操作,必须会用到EKEventStore、EKEvent、EKAlarm这几个类;

EKEventStore:相当于事件的储存器,事件和提醒写入到日历,必须要通过它的承载;
EKEvent:日历的一个事件;
EKAlarm:创建一个提醒;

下面直接上代码☞:

EKEventStore *eventStore = [[EKEventStore alloc] init];
EKEvent *myEvent  = [EKEvent eventWithEventStore:eventStore]; 
myEvent.title     = title;  //标题
myEvent.location = location;
myEvent.notes = notes; //描述
myEvent.startDate = startDate;
myEvent.endDate =  endDate;
// 设置提醒
EKAlarm *alarm = [EKAlarm alarmWithRelativeOffset:((60*timeNumber.integerValue) * -1)];
[myEvent addAlarm:alarm]; // 多个提醒设置alarms属性就好

//添加calendar
[myEvent setCalendar:[eventStore defaultCalendarForNewEvents]]; 
//保存
[eventStore saveEvent:myEvent span:EKSpanThisEvent error:&err]; 

二、重复日程的写入

重复日程我们要用到EKRecurrenceRule,把重复规则去配置好,系统会自动生成对应的日程;在开发时,iOS实现与Android的不太一样,Android直接支持rfc5545Rrule规则来赋值实现(一句代码就搞定),iOS可就不一样了,必须要自己来算规则,不过不要慌,这些坑已经有前辈们帮我们踩过了;继续往下看👇:

2.1 自己配置规则

// 根据后台参数返回的规则,填充好initRecurrenceWithFrequency:frequency:方法的对应的参数即可,具体参数意思可在xcode中查看API的说明;
EKRecurrenceRule *rule = [[EKRecurrenceRule alloc] initRecurrenceWithFrequency:frequency
                                                                          interval:interval
                                                                     daysOfTheWeek:daysOfTheWeek
                                                                    daysOfTheMonth:daysOfTheMonth
                                                                   monthsOfTheYear:monthsOfTheYear
                                                                    weeksOfTheYear:weeksOfTheYear
                                                                     daysOfTheYear:daysOfTheYear
                                                                      setPositions:setPositions
                                                                               end:recurrenceEnd];

2.2 直接用rfc5545Rrule

规则转换参考文献-EKRecurrenceRule

// 使用方法如下:
EKRecurrenceRule *rule = [[EKRecurrenceRule alloc] initWithString:rfc5545Rrule];
[myEvent addRecurrenceRule:rule];
- (EKRecurrenceRule *)initWithString:(NSString *)rfc2445String {
    // If the date formatter isn't already set up, create it and cache it for reuse.
    if (dateFormatter == nil)
    {
        dateFormatter = [[NSDateFormatter alloc] init];
        NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
        
        [dateFormatter setLocale:enUSPOSIXLocale];
        [dateFormatter setDateFormat:@"yyyyMMdd'T'HHmmss'Z'"];
        [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
    }
    
    // Begin parsing
    NSArray *components = [rfc2445String.uppercaseString componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@";="]];

    EKRecurrenceFrequency frequency = EKRecurrenceFrequencyDaily;
    NSInteger interval              = 1;
    NSMutableArray *daysOfTheWeek   = nil;
    NSMutableArray *daysOfTheMonth  = nil;
    NSMutableArray *monthsOfTheYear = nil;
    NSMutableArray *daysOfTheYear   = nil;
    NSMutableArray *weeksOfTheYear  = nil;
    NSMutableArray *setPositions    = nil;
    EKRecurrenceEnd *recurrenceEnd  = nil;
    
    for (int i = 0; i < components.count; i++)
    {
        NSString *component = [components objectAtIndex:i];
        
        // Frequency
        if ([component isEqualToString:@"FREQ"])
        {
            NSString *frequencyString = [components objectAtIndex:++i];
            
            if      ([frequencyString isEqualToString:@"DAILY"])   frequency = EKRecurrenceFrequencyDaily;
            else if ([frequencyString isEqualToString:@"WEEKLY"])  frequency = EKRecurrenceFrequencyWeekly;
            else if ([frequencyString isEqualToString:@"MONTHLY"]) frequency = EKRecurrenceFrequencyMonthly;
            else if ([frequencyString isEqualToString:@"YEARLY"])  frequency = EKRecurrenceFrequencyYearly;
        }
    
        // Interval
        if ([component isEqualToString:@"INTERVAL"])
        {
            interval = [[components objectAtIndex:++i] intValue];
        }
        
        // Days of the week
        if ([component isEqualToString:@"BYDAY"])
        {
            daysOfTheWeek = [NSMutableArray array];
            NSArray *dayStrings = [[components objectAtIndex:++i] componentsSeparatedByString:@","];
            for (NSString *dayString in dayStrings)
            {
                
                if(dayString && ![dayString isEqualToString:@""] && dayString.length > 0){
                    int dayOfWeek = 0;
                    int weekNumber = 0;
                    
                    // Parse the day of the week
                    if ([dayString rangeOfString:@"SU"].location != NSNotFound)      dayOfWeek = 1;
                    else if ([dayString rangeOfString:@"MO"].location != NSNotFound) dayOfWeek = 2;
                    else if ([dayString rangeOfString:@"TU"].location != NSNotFound) dayOfWeek = 3;
                    else if ([dayString rangeOfString:@"WE"].location != NSNotFound) dayOfWeek = 4;
                    else if ([dayString rangeOfString:@"TH"].location != NSNotFound) dayOfWeek = 5;
                    else if ([dayString rangeOfString:@"FR"].location != NSNotFound) dayOfWeek = 6;
                    else if ([dayString rangeOfString:@"SA"].location != NSNotFound) dayOfWeek = 7;
                    
                    // Parse the week number
                    weekNumber = [[dayString substringToIndex:dayString.length-2] intValue];
                    
                    [daysOfTheWeek addObject:[EKRecurrenceDayOfWeek dayOfWeek:dayOfWeek weekNumber:weekNumber]];
                }
                
            }
        }
        
        // Days of the month
        if ([component isEqualToString:@"BYMONTHDAY"])
        {
            daysOfTheMonth = [NSMutableArray array];
            NSArray *dayStrings = [[components objectAtIndex:++i] componentsSeparatedByString:@","];
            for (NSString *dayString in dayStrings)
            {
                [daysOfTheMonth addObject:[NSNumber numberWithInt:dayString.intValue]];
            }
        }
        
        // Months of the year
        if ([component isEqualToString:@"BYMONTH"])
        {
            monthsOfTheYear = [NSMutableArray array];
            NSArray *monthStrings = [[components objectAtIndex:++i] componentsSeparatedByString:@","];
            for (NSString *monthString in monthStrings)
            {
                [monthsOfTheYear addObject:[NSNumber numberWithInt:monthString.intValue]];
            }
        }
        
        // Weeks of the year
        if ([component isEqualToString:@"BYWEEKNO"])
        {
            weeksOfTheYear = [NSMutableArray array];
            NSArray *weekStrings = [[components objectAtIndex:++i] componentsSeparatedByString:@","];
            for (NSString *weekString in weekStrings)
            {
                [weeksOfTheYear addObject:[NSNumber numberWithInt:weekString.intValue]];
            }
        }
        
        // Days of the year
        if ([component isEqualToString:@"BYYEARDAY"])
        {
            daysOfTheYear = [NSMutableArray array];
            NSArray *dayStrings = [[components objectAtIndex:++i] componentsSeparatedByString:@","];
            for (NSString *dayString in dayStrings)
            {
                [daysOfTheYear addObject:[NSNumber numberWithInt:dayString.intValue]];
            }
        }
        
        // Set positions
        if ([component isEqualToString:@"BYSETPOS"])
        {
            setPositions = [NSMutableArray array];
            NSArray *positionStrings = [[components objectAtIndex:++i] componentsSeparatedByString:@","];
            for (NSString *potitionString in positionStrings)
            {
                [setPositions addObject:[NSNumber numberWithInt:potitionString.intValue]];
            }
        }
        
        // RecurrenceEnd
        if ([component isEqualToString:@"COUNT"])
        {
            NSUInteger occurenceCount = [[components objectAtIndex:++i] intValue];
            recurrenceEnd = [EKRecurrenceEnd recurrenceEndWithOccurrenceCount:occurenceCount];
            
        } else if ([component isEqualToString:@"UNTIL"])
        {
            NSDate *endDate =  [dateFormatter dateFromString:[components objectAtIndex:++i]];
            recurrenceEnd = [EKRecurrenceEnd recurrenceEndWithEndDate:endDate];
        }
    }
    
    EKRecurrenceRule *rule = [[EKRecurrenceRule alloc] initRecurrenceWithFrequency:frequency
                                                                          interval:interval
                                                                     daysOfTheWeek:daysOfTheWeek
                                                                    daysOfTheMonth:daysOfTheMonth
                                                                   monthsOfTheYear:monthsOfTheYear
                                                                    weeksOfTheYear:weeksOfTheYear
                                                                     daysOfTheYear:daysOfTheYear
                                                                      setPositions:setPositions
                                                                               end:recurrenceEnd];
    
    return rule;
}

三、全天日程的写入

写入全天日程用一个属性叫:allDay;

myEvent.allDay = YES;

四、系统日历中自定义的日程读取及删除

删除日程,只会删除你自己创建的日程,系统默认已写好的日程是无权删除的;

EKEventStore *eventStore = [[EKEventStore alloc] init];
    // 获取到此事件
    NSArray * eventArray = [eventStore calendarsForEntityType:EKEntityTypeEvent];
    NSMutableArray * onlyArray = [NSMutableArray array];
    for (int i=0; i<eventArray.count; i++) {
        EKCalendar * tempCalendar = eventArray[i];
        EKCalendarType type = tempCalendar.type;
        if (type == EKCalendarTypeCalDAV) {
            [onlyArray addObject:tempCalendar];
        }
    }
    
    NSPredicate * predicate = [eventStore predicateForEventsWithStartDate:startDate endDate:endDate calendars:onlyArray];
    NSArray * events = [eventStore eventsMatchingPredicate:predicate];

for (int i = 0; i < events.count; i++) {
        // 删除这一条事件
        EKEvent *event = events[i];
        // commit:NO:最后再一次性提交
        [eventStore removeEvent:event span:EKSpanThisEvent commit:NO error:&error];
}
//一次提交所有操作到事件库
NSError *errored = nil;
BOOL commitSuccess = [eventStore commit:&errored];

到这里,我们日程的一些基本的操作都已经介绍完了,你以为这就完了,那就错了,如果你按照这样写进去,测试只要稍微测一下,就会给你提一堆问题,所以自测一定很重要,场景什么都自己过一遍,然后再提测,这样后面自己改的时候也能有底。继续往下看👇:

五、⚠️注意的地方⚠️

5.1 iPhone 时间设置12小时制咋搞❓❓

把我们拿到的时间戳转时间的时候,把时间格式写成@"YYYY-MM-dd HH:mm:ss"样式就可以了,这里要注意点就是小时那一部分,一定要写成大写的HH,这样系统就能自动识别出是12小时制,还是24小时制了;

+ (NSDate *)dateWithTimestamp:(NSTimeInterval)timestamp format:(NSString *)format {
    NSDate *date = [NSDate dateWithTimeIntervalSince1970:(timestamp)];
    NSDateFormatter *dateFmt = [[NSDateFormatter alloc] init];
    if (format.length) {
        dateFmt.dateFormat = format;
    } else {
        dateFmt.dateFormat = @"YYYY-MM-dd HH:mm:ss";
    }
    return date;
}

5.2 全天的开始时间/结束时间设了咋没用❓❓

在开始的时候,把时间算成从0点开始到23:59结束,系统直接给过滤掉了,后来发现,直接开始时间和结束时间都给年月日零点的格式就ok了;

+ (NSDate *)dateWithTimestamp:(NSTimeInterval)timestamp format:(NSString *)format onlyYearMonthDay:(BOOL)only {
    NSDate *date = [self dateWithTimestamp:timestamp format:format];
    if (only) {
        NSTimeInterval interval = [date timeIntervalSince1970];
        int daySeconds = 24 * 60 * 60;
        NSInteger allDays = interval / daySeconds;
        return [NSDate dateWithTimeIntervalSince1970:allDays * daySeconds];
    }
    return date;
}

5.3 全天提醒设置后怎么就不是我想要的呢❓❓

系统默认的开始时间和结束时间与中国的北京时间是有时差的,所以我们一定要加上对应的时差(具体看后台数据);

// 例如:
myEvent.startDate = [NSDate dateWithTimestamp:(startInterval+8*60*60) format:@"YYYY-MM-dd HH:mm:ss" onlyYearMonthDay:YES];
myEvent.endDate = [NSDate dateWithTimestamp:(endInterval+8*60*60) format:@"YYYY-MM-dd HH:mm:ss" onlyYearMonthDay:YES];

5.4 为啥后台给我了好几个提醒,最后就显示2个提醒❓❓

iPhone上默认只显示2条提醒,自己直接日历创建日程也只会显示2条提醒;
资料有说Mac会显示多条,具体没有验证,小伙伴可以自己验证一下,欢迎小伙伴告诉到评论区😁;

5.5 为啥就设置了标题和定位,就默认给我来个提醒:"出发时间"❓❓

这个也是系统默认的,只要你设置了定位,没有设置提醒项,系统就回默认给你填充一个"出发时间"的提醒;(当初在查问题的时候卡了很久😭😭)

关于日历的操作就分享到这里了,如果有什么可以补充的或疑问欢迎评论区留言,希望这篇文章可以帮助到有需要的你们。分享只是一种美德,我们应该多分享只是给身边的朋友,大家一起成长。

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

推荐阅读更多精彩内容