美文网首页搬砖iOS开发iOS大咖说
iOS-使用FSCalendar实现日历签到功能

iOS-使用FSCalendar实现日历签到功能

作者: Teun丶 | 来源:发表于2017-11-28 18:20 被阅读0次

    最终效果: 效果.gif

    请大家忽略图片质量哈,我这软件弄出来的质量不高.最终实现的就是在客户端能够签到功能,使用了以为大神封装的日历类,FSCalendar,附github地址:FSCalendar

    我的demo基本就是一个使用FSCalendar的一个样例,但是直接使用中会有一些坑,话不多说,直接上代码

    1.首先安装FSCalendar

    pod 'FSCalendar', '~> 2.7.9'
    

    2.创建一个新类,导入FSCalendar和系统事件库EventKit

    #import "FSCalendar.h"
    //用来读取,修改和创建日历上的事件
    #import <EventKit/EventKit.h>
    

    3.重写loadView方法,创建FSCalendar

      //创建日历类
        FSCalendar *calendar = [[FSCalendar alloc] initWithFrame:CGRectMake(0, self.navigationController.navigationBar.frame.size.height, self.view.bounds.size.width - 50, 300)];
        calendar.backgroundColor = [UIColor whiteColor];
        calendar.dataSource = self;
        calendar.delegate = self;
        //日历语言为中文
        calendar.locale = [NSLocale localeWithLocaleIdentifier:@"zh-CN"];
        //允许多选,可以选中多个日期
        calendar.allowsMultipleSelection = YES;
        //如果值为1,那么周日就在第一列,如果为2,周日就在最后一列
        calendar.firstWeekday = 1;
        //周一\二\三...或者头部的2017年11月的显示方式
        calendar.appearance.caseOptions = FSCalendarCaseOptionsWeekdayUsesSingleUpperCase|FSCalendarCaseOptionsHeaderUsesUpperCase;
        [self.view addSubview:calendar];
        self.calendar = calendar;
    

    4.根据创建的日历类做初始化设置

    #pragma mark - <配置日历>
    - (void)calendarConfig{
        //创建系统日历类
        self.gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
        //获取日历要显示的日期范围
        NSArray *timeArray = [ViewController getStartTimeAndFinalTime];
        //设置最小和最大日期(在最小和最大日期之外的日期不能被选中,日期范围如果大于一个月,则日历可翻动)
        self.minimumDate = [self.dateFormatter dateFromString:timeArray[0]];
        self.maximumDate = [self.dateFormatter dateFromString:timeArray[1]];
        self.calendar.accessibilityIdentifier = @"calendar";
        //title显示方式
        self.calendar.appearance.headerDateFormat = @"yyyy年MM月";
        //关闭字体自适应,设置字体大小\颜色
        self.calendar.appearance.adjustsFontSizeToFitContentSize = NO;
        self.calendar.appearance.subtitleFont = [UIFont systemFontOfSize:8];
        self.calendar.appearance.headerTitleColor = [UIColor whiteColor];
        self.calendar.appearance.weekdayTextColor = [UIColor whiteColor];
        self.calendar.appearance.selectionColor = [UIColor orangeColor];
        //日历头部颜色
        self.calendar.calendarHeaderView.backgroundColor = themeColor;
        self.calendar.calendarWeekdayView.backgroundColor = themeColor;
    }
    

    5.实现FSCalendar数据源方法

    #pragma mark - FSCalendarDataSource
    //日期范围(最小)
    - (NSDate *)minimumDateForCalendar:(FSCalendar *)calendar
    {
        return self.minimumDate;
    }
    //日期范围(最大)
    - (NSDate *)maximumDateForCalendar:(FSCalendar *)calendar
    {
        return self.maximumDate;
    }
    

    6.(重点)签到逻辑


    Simulator Screen Shot - iPhone 8 - 2017-11-28 at 17.04.50.png
    - (void)viewDidLoad {
        [super viewDidLoad];
        //日历配置
        [self calendarConfig];
        //1.加载缓存中的的日期,并选中这些日期
        [self getCache];
        //2.从网络获取其签到结果,如果发现请求的结果中存在没有被选中,就将其选中,并加载到缓存中
        [self getSign];
    }
    

    上述两个方法的具体实现大致思路为:

    • 当控制器加载完毕后,从缓存获取数据并让日历选中
    • 从服务器获取一次该用户的签到结果,检查是否有遗漏(考虑到当用户在其他设备登录时),如果有遗漏添加到缓存中,并选中
    • 缓存策略,如果不存缓存的话,每次启动APP后加载签到页面,就要重新网络请求获取签到数据,并选中日期,每次动画都要延时出现不太合适,所以存缓存,缓存可以让签到结果快速加载

    具体实现如下

    //加载缓存
    - (void)getCache{
        //从缓存中先把数据取出来
        NSString *key = [NSString stringWithFormat:@"arrayDate"];
        NSMutableArray *cache = [[NSUserDefaults standardUserDefaults] objectForKey:key];
        //允许用户选择,其实是允许系统来选中签到日期
        self.calendar.allowsSelection = YES;
        self.calendar.allowsMultipleSelection = YES;
        if (cache.count) {//如果cache里面有数据
            //选中日期,只有不在选中之列的才去选中它
            for (NSInteger i = 0; i<cache.count; i++) {
                if (![self.calendar.selectedDates containsObject:cache[i]]) {
                    [self.calendar selectDate:cache[i]];
                }
            }
        }else{//如果cache里面没有数据,说明第一次启动
            //创建个可变数组储存进缓存
            NSMutableArray *cache = [NSMutableArray array];
            [[NSUserDefaults standardUserDefaults] setValue:cache forKey:key];
            [[NSUserDefaults standardUserDefaults] synchronize];
        }
        //选择完毕后关闭可选项,不让用户自己点
        self.calendar.allowsSelection = NO;
    }
    
    
    //点击签到按钮的Action
    - (void)signInAction{
        //假设在这里网络请求签到成功,成功后需要重新请求签到所有结果
        if (_count>31) {//此处的判断仅为本demo临时使用,正式使用中可以根据具体情况删除此if else判断
            NSLog(@"别点了");
            return;
        }else if (!_count){
            _count = 1;
        }
        NSString *dateStr = [NSString stringWithFormat:@"2017-11-%ld",_count];
        _count++;
        [self.signInList addObject:dateStr];
        [self getSign];
    }
    
    
    //从网络获取所有签到结果
    - (void)getSign{
        //配置日期缓存的key
        NSString *key = [NSString stringWithFormat:@"arrayDate"];
        
        //在这里假装网络请求所有的签到结果(signInList)成功了
        NSLog(@"%@",_signInList);
        //获取签到总数量
        self.SignCount = _signInList.count;
        //常见临时数组dataArrayCache,用于存放签到结果(可能有的人觉得这一步不需要,但是咱们假设的签到结果里面只有纯日期,正式项目中可不一定如此)
        NSMutableArray *dataArrayCache = [NSMutableArray array];
        
        if (self.SignCount) {//如果请求的数据有效
            for (NSString *dateStr in _signInList) {
                //把所有签到数据取出来添加进临时数组
                NSDate *date = [self.dateFormatter dateFromString:dateStr];
                if(date){
                    [dataArrayCache addObject:date];
                }
            }
            //用偏好设置保存签到数据到本地缓存
            [[NSUserDefaults standardUserDefaults] setValue:dataArrayCache forKey:key];
            [[NSUserDefaults standardUserDefaults] synchronize];
            //保存后重新加载缓存数据
            [self getCache];
        }
    }
    
    //获取日历范围,让日历出现时就知道该显示哪个月了哪一页了(根据系统时间来获取)
    +(NSArray *)getStartTimeAndFinalTime{
        NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
        [formatter setDateFormat:@"YYYY-MM-dd"];
        NSDate *datenow = [NSDate date];
        NSString *currentTimeString = [formatter stringFromDate:datenow];
        NSDate *newDate=[formatter dateFromString:currentTimeString];
        double interval = 0;
        NSDate *firstDate = nil;
        NSDate *lastDate = nil;
        NSCalendar *calendar = [NSCalendar currentCalendar];
        BOOL OK = [calendar rangeOfUnit:NSCalendarUnitMonth startDate:& firstDate interval:&interval forDate:newDate];
        if (OK) {
            lastDate = [firstDate dateByAddingTimeInterval:interval - 1];
        }else {
            return @[@"",@""];
        }
        NSString *firstString = [formatter stringFromDate: firstDate];
        NSString *lastString = [formatter stringFromDate: lastDate];
        //返回数据为日历要显示的最小日期firstString和最大日期lastString
        return @[firstString, lastString];
    }
    

    7.关于FSCalendar的代理方法基本不需要实现,因为签到一般不允许用户点击,如果有特殊需求的话,代价可以加上,更多的可以去delegate中寻找需要用的

    #pragma mark - FSCalendarDelegate
    //手动选中了某个日期(本demo暂时被隐藏)
    - (void)calendar:(FSCalendar *)calendar didSelectDate:(NSDate *)date atMonthPosition:(FSCalendarMonthPosition)monthPosition
    {
        NSLog(@"did select %@",[self.dateFormatter stringFromDate:date]);
    }
    //当前页被改变,日历翻动时调用(本demo暂时没用到)
    - (void)calendarCurrentPageDidChange:(FSCalendar *)calendar
    {
        NSLog(@"did change page %@",[self.dateFormatter stringFromDate:calendar.currentPage]);
    }
    

    8.显示农历:将LunarFormatter拖进项目,FSCalendar的demo中也有,本文demo中也有,主要在数据源方法中使用


    image.png
    //数据源方法,根据是否显示节日和农历
    - (NSString *)calendar:(FSCalendar *)calendar subtitleForDate:(NSDate *)date
    {
        if (self.showsEvents) {//如果要求显示节日
            EKEvent *event = [self eventsForDate:date].firstObject;
            if (event) {
                return event.title;
            }
        }
        if (self.showsLunar) {//如果要求显示农历
            return [self.lunarFormatter stringFromDate:date];
        }
        return nil;
    }
    //加载节日到日历中
    - (void)loadCalendarEvents
    {
        __weak typeof(self) weakSelf = self;
        EKEventStore *store = [[EKEventStore alloc] init];
        //请求访问日历
        [store requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
            //允许访问
            if(granted) {
                NSDate *startDate = self.minimumDate;
                NSDate *endDate = self.maximumDate;
                NSPredicate *fetchCalendarEvents = [store predicateForEventsWithStartDate:startDate endDate:endDate calendars:nil];
                NSArray<EKEvent *> *eventList = [store eventsMatchingPredicate:fetchCalendarEvents];
                NSArray<EKEvent *> *events = [eventList filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(EKEvent * _Nullable event, NSDictionary<NSString *,id> * _Nullable bindings) {
                    return event.calendar.subscribed;
                }]];
                
                dispatch_async(dispatch_get_main_queue(), ^{
                    if (!weakSelf) return;
                    weakSelf.events = events;
                    [weakSelf.calendar reloadData];
                });
                
            } else {
                
                // Alert
                UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"权限错误" message:@"获取节日事件需要权限呀大宝贝!" preferredStyle:UIAlertControllerStyleAlert];
                [alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]];
                [self presentViewController:alertController animated:YES completion:nil];
            }
        }];
        
    }
    //根据日期来显示事件
    - (NSArray<EKEvent *> *)eventsForDate:(NSDate *)date
    {
        NSArray<EKEvent *> *events = [self.cache objectForKey:date];
        if ([events isKindOfClass:[NSNull class]]) {
            return nil;
        }
        NSArray<EKEvent *> *filteredEvents = [self.events filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(EKEvent * _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
            return [evaluatedObject.occurrenceDate isEqualToDate:date];
        }]];
        if (filteredEvents.count) {
            [self.cache setObject:filteredEvents forKey:date];
        } else {
            [self.cache setObject:[NSNull null] forKey:date];
        }
        return filteredEvents;
    }
    

    9.最后获取日历权限需要在info.plist文件配置Privacy - Calendars Usage Description获取日历使用权限
    10.demo地址:https://github.com/TynnPassBy/TynnSignCalendar,下载完后记得pod install后再启动项目,有任何疑问可以在下方留言,我会尽力帮助大家.附FSCalendar两篇比较使用的文章:http://www.jianshu.com/p/59c5d535a710http://www.jianshu.com/p/6f1592260d35,感谢~

    相关文章

      网友评论

        本文标题:iOS-使用FSCalendar实现日历签到功能

        本文链接:https://www.haomeiwen.com/subject/tagcmttx.html