iOS 使用FSCalendar實現日曆簽到功能

最終效果:
效果.gif

請你們忽略圖片質量哈,我這軟件弄出來的質量不高.最終實現的就是在客戶端可以簽到功能,使用了覺得大神封裝的日曆類,FSCalendar,附github地址:FSCalendargit

個人demo基本就是一個使用FSCalendar的一個樣例,可是直接使用中會有一些坑,話很少說,直接上代碼github

1.首先安裝FSCalendar數組

pod 'FSCalendar', '~> 2.7.9'
複製代碼

2.建立一個新類,導入FSCalendar和系統事件庫EventKit緩存

#import "FSCalendar.h"
//用來讀取,修改和建立日曆上的事件
#import <EventKit/EventKit.h>
複製代碼

3.重寫loadView方法,建立FSCalendarbash

//建立日曆類
    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.(重點)簽到邏輯 app

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];
}
複製代碼

上述兩個方法的具體實現大體思路爲:async

  • 當控制器加載完畢後,從緩存獲取數據並讓日曆選中
  • 從服務器獲取一次該用戶的簽到結果,檢查是否有遺漏(考慮到當用戶在其餘設備登陸時),若是有遺漏添加到緩存中,並選中
  • 緩存策略,若是不存緩存的話,每次啓動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地址:github.com/TynnPassBy/… install後再啓動項目,有任何疑問能夠在下方留言,我會盡力幫助你們.

  • 附FSCalendar兩篇比較實用的文章:

www.jianshu.com/p/59c5d535a…

www.jianshu.com/p/6f1592260…

感謝~

相關文章
相關標籤/搜索