iOS高可控性日历基础组件-SKCalendarView的使用和实现思路的分享

简述

SKCalendarView是一个高可控性的日历基础组件,为了提高应用的自由度,默认只提供了日历部分的视图封装,但不涵盖切换月份按钮、年月分显示等非关键性控件,但请不要担心,SKCalendarView为你提供了多样性的API,你可以很轻松的拿到这些信息去展示在你自己的自定义控件中,以及对当前UI的修改:如:替换日历主题图片、节假日或特殊日期的日历背景、各种现实颜色等等。不仅如此,SKCalendarView还为你封装了公历农历节假日以及中国24节气的核心算法,即使你觉得默认的视图并不合胃口,也可以直接快速的利用这套算法创造出一个全新的日历控件。最后,SKCalendarView还提供了一些简单的切换动画,如果你不喜欢它,可以忽略掉,用自己的,这里完全不会受到任何限制。如果觉得还不错,点个star吧~

iOS高可控性日历基础组件-SKCalendarView的使用和实现思路的分享_第1张图片

效果图

最新更新:

  • 发布了0.0.3版本

  • 对日历算法部分进行优化,完全解决线程卡顿问题

  • 修复了指定日期查寻在显示上的若干问题

一.如何使用

1.如何开始

1.从GitHub上Clone-->SKCalendarView, 然后查看Demo (由于使用cocoaPods管理,请打开xcworkspace工程进行查看)

2.在项目中使用SKCalendarView,直接将目录下的SKCalendarView文件夹拷贝到工程中,或在podfile文件中添加pod 'SKCalendarView'

3.SKCalendarView的默认视图基于Masonry布局,如果需要使用, 请确保你的工程里已存在Masonry,下载地址

4.如果遇到其它问题,欢迎提交issues,我会及时回复

2.使用方法

头文件导入

#import "SKConstant.h"

继承SKCalendarView

@property (nonatomic, strong) SKCalendarView * calendarView;

日历设置

_calendarView.calendarTodayTitleColor = [UIColor redColor];// 今天标题字体颜色
_calendarView.calendarTodayTitle = @"今日";// 今天下标题
_calendarView.dateColor = [UIColor orangeColor];// 今天日期数字背景颜色
_calendarView.calendarTodayColor = [UIColor whiteColor];// 今天日期字体颜色
_calendarView.dayoffInWeekColor = [UIColor redColor];
_calendarView.springColor = [UIColor colorWithRed:48 / 255.0 green:200 / 255.0 blue:104 / 255.0 alpha:1];// 春季节气颜色
_calendarView.summerColor = [UIColor colorWithRed:18 / 255.0 green:96 / 255.0 blue:0 alpha:8];// 夏季节气颜色
_calendarView.autumnColor = [UIColor colorWithRed:232 / 255.0 green:195 / 255.0 blue:0 / 255.0 alpha:1];// 秋季节气颜色
_calendarView.winterColor = [UIColor colorWithRed:77 / 255.0 green:161 / 255.0 blue:255 / 255.0 alpha:1];// 冬季节气颜色
_calendarView.holidayColor = [UIColor redColor];//节日字体颜色
self.lastMonth = _calendarView.lastMonth;// 获取上个月的月份
self.nextMonth = _calendarView.nextMonth;// 获取下个月的月份

翻页动画

[SKCalendarAnimationManage animationWithView:self.calendarView andEffect:SK_ANIMATION_REVEAL isNext:YES];

获取农历年

self.chineseYearLabel.text = [NSString stringWithFormat:@"%@年", self.calendarView.chineseYear];// 农历年

获取农历月日

self.chineseMonthAndDayLabel.text = [NSString stringWithFormat:@"%@%@", self.calendarView.chineseMonth, getNoneNil(self.calendarView.chineseCalendarDay[row])];

获取公历年/月

self.yearLabel.text = [NSString stringWithFormat:@"%@年%@月", @(self.calendarView.year), @(self.calendarView.month)];// 公历年

获取节日/节气

self.holidayLabel.text = [self.calendarView getHolidayAndSolarTermsWithChineseDay:getNoneNil(self.calendarView.chineseCalendarDay[row])];

查询指定日期

[self.calendarView checkCalendarWithAppointDate:[NSDate date]];

日历UI配置

@property (nonatomic, strong) UIColor * weekBackgroundColor;// 周的背景颜色
@property (nonatomic, strong) UIColor * normalInWeekColor;// 周(除双休日外)字体颜色
@property (nonatomic, strong) UIColor * dayoffInWeekColor;// 双休日字体颜色
@property (nonatomic, strong) UIColor * calendarTodayColor;// 本日日期字体颜色
@property (nonatomic, strong) UIColor * dateColor;// 日期小背景颜色
@property (nonatomic, strong) UIImage * dateIcon;// 日期图片
@property (nonatomic, strong) UIColor * holidayBackgroundColor;// 节日背景颜色
@property (nonatomic, strong) UIColor * solarTeromBackgroundColor;// 节气背景颜色
@property (nonatomic, strong) UIColor * dateBackgroundColor;// 日期背景颜色(非节日&节气)
@property (nonatomic, strong) UIImage * dateBackgroundIcon;// 日期背景图片
@property (nonatomic, strong) NSString * calendarTodayTitle;// 本日日期标题
@property (nonatomic, strong) UIColor * calendarTodayTitleColor;// 本日日期标题字体颜色
@property (nonatomic, strong) UIColor * calendarTitleColor;// 日期标题字体颜色
@property (nonatomic, strong) UIColor * holidayColor;// 节日标题字体颜色
@property (nonatomic, strong) UIColor * springColor;// 春季节气颜色
@property (nonatomic, strong) UIColor * summerColor;// 夏季节气颜色
@property (nonatomic, strong) UIColor * autumnColor;// 秋季节气颜色
@property (nonatomic, strong) UIColor * winterColor;// 冬季节气颜色
@property (nonatomic, assign) BOOL enableClickEffect;// 开启点击效果
@property (nonatomic, assign) BOOL enableDateRoundCorner;// 开启日期圆角

获取点击到的日期

注意:这里需要先遵循代理协议

- (void)selectDateWithRow:(NSUInteger)row
```

# 二.如何实现


## 1.设计思路
- 总体上SKCalendarView仍然才去模块化思路,主要分为三个部分`View`(视图)、`Animation`(动画)以及`Algorithm`(算法)

- `View`主要负责处理外部对UI的配置信息、日历核心部分的展示、UI的刷新、效果的处理和界面控件的创建和布局约束等

- `Animation`主要负责日历翻页时的动画效果及点击日期的动画效果的处理

- `Algorithm`是整个`SKCalendarView`最核心的部分,负责了`公历`、`农历`、`节假日`以及`中国24节气`的核心算法,以及对日期查询的处理反馈

## 2.功能实现


### 2.1布局

#### 思路
 我们先要搞清楚日历是什么。所谓日历,就是一年当中12个月份的日期展示,每个月当中的日期数量由28~31天不等,这里指的是公历, 而农历当中每个月最多30天,虽然在计算方法上是有很大差别,但好在当代日历都是以公历为展示基准,所以只需要考虑公历的每月天数。

 因为要考虑到展示上的美观性,一般都是采用正方形来展示,由于一周是固定的7天,所以我们日历的横向子控件数量也必须为7。但是这样问题就来了,由于需要考虑到与日期上方的周时间相对应,并且除了2月没有哪个月是的天数的7的倍数,也就做不到整除而导致无法形成正方形布局,所以我们不能直接用和月份天数相等的子控件数量来展示我们的日历,经过思考,我决定采取填充数据的方式来达到正方形展示的目的:
 - 首先规划整体子控件数量,由于横向固定是7,那么纵向就由最多的一个月31天算,31 / 7 ≈ 4.4, 既然超过了4行,那么我们就放5行吧: 5 x 7 = 35,子控件放置35个如何?但经过尝试后,发现这并不可取:因为我们这里理想状态下的31天是以这个月的第一天恰好是周日 (周日为公历一周的开始) 为前提条件的,那么显然在现实生活里并不可能每个月都恰好第一天都是周日,所以,我们就需要考虑到`需要显示的这个月的第一天是周几`这个问题,众所周知,一周有7天的时间,那么每个月的第一天就有7种可能。做最多的打算,假设这个月总共有31天,而第一天恰好是周六,那么在这个月的1日这一天之前就有6天是没有日期的,结合我们之前计算的数量加上周六前的6天: 35 + 6 = 41,子控件放41个又如何呢?当然是不行了,因为需要正方形的日历,所以至少要成为7的倍数,最接近这个倍数的值就是我们要的答案:42.

#### 实现
- 在基础控件的布局上,我们采取最简便的方式:周和日期我们分别使用了`weekCollectionView`、`calendarCollectionView`这两个`UICollectionView`来完成

- 而月份的背景数字`monthBackgroundLabel`作为最上面一层采用的是`UILabel`,在设置了其`size`和`weight`后,效果就如同背景图一样
```objectivec
// 周
    UICollectionViewFlowLayout * layout = [[UICollectionViewFlowLayout alloc] init];
    layout.scrollDirection = UICollectionViewScrollDirectionVertical;
    self.weekCollectionView = [[UICollectionView alloc] initWithFrame:self.frame collectionViewLayout:layout];
    [self addSubview:self.weekCollectionView];
    self.weekCollectionView.backgroundColor = [UIColor whiteColor];
    self.weekCollectionView.delegate = self;
    self.weekCollectionView.dataSource = self;
    [self.weekCollectionView registerClass:[SKWeekCollectionViewCell class] forCellWithReuseIdentifier:@"Week"];
    [self.weekCollectionView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self);
        make.left.equalTo(self);
        make.right.equalTo(self);
        
        make.height.mas_offset(self.frame.size.height / 7.5);
        make.height.mas_greaterThanOrEqualTo(40).priorityHigh();
    }];

    // 日期
    UICollectionViewFlowLayout * dateLayout = [[UICollectionViewFlowLayout alloc] init];
    dateLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
    self.calendarCollectionView = [[UICollectionView alloc] initWithFrame:self.frame collectionViewLayout:dateLayout];
    [self addSubview:self.calendarCollectionView];
    self.calendarCollectionView.backgroundColor = [UIColor whiteColor];
    self.calendarCollectionView.delegate = self;
    self.calendarCollectionView.dataSource = self;
    [self.calendarCollectionView registerClass:[SKCalendarCollectionViewCell class] forCellWithReuseIdentifier:@"Calendar"];
    [self.calendarCollectionView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.weekCollectionView.mas_bottom);
        make.left.equalTo(self);
        make.right.equalTo(self);
        make.bottom.equalTo(self);
    }];
    
    // 背景月份
    self.monthBackgroundLabel = [UILabel new];
    [self addSubview:self.monthBackgroundLabel];
    self.monthBackgroundLabel.textColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:200 / 2550.f];
    self.monthBackgroundLabel.font = [UIFont systemFontOfSize:150.0f weight:120.f];
    self.monthBackgroundLabel.textAlignment = NSTextAlignmentCenter;
    [self.monthBackgroundLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self).with.insets(UIEdgeInsetsMake(0, 0, 0, 0));
    }];
```

#### 对日历高度的控制
- 由于不同的月份的第一天所处的周时间不同,导致日历的`有效日期` (有日期显示的) 行数不固定,如:当本月第一天为周日时,最多只占35个子控件位数,而我们一开始设置的子控件数量值是42,这样一来就会空出一行的空白出来,这是很不美观的。所以日历的高度对于我们来说就是一个把控的值,如何来保证可以根据每个月的天数来控制日历的高度呢,在`SKCalendarView`中采取了以下的办法:
```objectivec
if (self.calendarManage.isIncreaseHeight == YES) {// 根据isIncreaseHeight来判断是否需要更改高度
            [self.calendarCollectionView mas_updateConstraints:^(MASConstraintMaker *make) {
                make.height.mas_offset(6 * (self.frame.size.height / 7.5));
            }];
            return 42;
            
        } else {
            if (self.calendarCollectionView.frame.size.height > 218) {
                [self.calendarCollectionView mas_updateConstraints:^(MASConstraintMaker *make) {
                    make.height.mas_offset(5 * (self.frame.size.height / 7.5));
                }];
            }
            return 35;
        }
```

#### 日期点击效果的处理
- 在`SKCalendarCollectionViewCell`的内部,我们将`enableClickEffect`(是否开启点击效果)为YES的状态设为开启效果,并调用动画管理类`SKCalendarAnimationManage`的方法
```objectivec
[SKCalendarAnimationManage clickEffectAnimationForView:self.baseView];
```


### 2.2 日历算法

##### 这一部分算法是整个SKCalendarView最核心的部分

- `SKCalendarManage`以单例的模式封装了`SKCalendarView`全部的核心算法

- 主要难点在于对个别不定期节日,如复活节的日期的计算等,以及24节气和农历的计算,推荐阅读[《算法:计算中国农历》](http://blog.csdn.net/orbit/article/details/9337377)

- 查看所选日期所处的月份:

```objectivec
#pragma mark - 查看所选日期所处的月份
- (void)checkThisMonthRecordFromToday:(NSDate *)today
{
    if (isEmpty(today)) {// 如果没有日期,默认今天
        today = [NSDate date];
    }
    
    [self calculationThisMonthDays:today];// 计算本月天数
    [self calculationThisMonthFirstDayInWeek:today];// 计算本月第一天是周几
}
```

- 计算本月天数

```objectivec
#pragma mark - 计算本月天数
- (void)calculationThisMonthDays:(NSDate *)days
{
    NSCalendar * calendar = [NSCalendar currentCalendar];
    if (isEmpty(days)) {
        days = [NSDate date];
    }
    NSRange range = [calendar rangeOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitMonth forDate:days];
    self.days = range.length;// 保存天数
}
```


- 计算本月第一天是周几

```objectivec
#pragma mark - 计算本月第一天是周几
- (void)calculationThisMonthFirstDayInWeek:(NSDate *)date;
{
    if (isEmpty(date)) {
        date = [NSDate date];
    }
    NSCalendar * calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
    NSDateComponents * comps = [[NSDateComponents alloc] init];
    NSDateComponents * theComps = [[NSDateComponents alloc] init];
    NSInteger unitFlags = NSCalendarUnitDay | NSCalendarUnitWeekday | NSCalendarUnitMonth | NSCalendarUnitYear;
    comps = [calendar components:unitFlags fromDate:date];
    theComps = [calendar components:unitFlags fromDate:[NSDate date]];
    self.theMonth = [theComps month];// 本月的月份
    NSUInteger day = [comps day];// 是本月第几天
    self.todayInMonth = day;
    if (day > 1) {// 如果不是本月第一天
        // 将日期推算到本月第一天
        NSInteger hours = (day - 1) * -24;
        date = [NSDate dateWithTimeInterval:hours * 60 * 60 sinceDate:date];
    }
    comps = [calendar components:unitFlags fromDate:date];
    self.dayInWeek = [comps weekday];// 是周几
    self.year = [comps year];// 公历年
    self.month = [comps month];// 公里月

    [self creatcalendarArrayWithDate:date];// 创建日历数组
}
```


- 创建日历数组(公历、农历)

这里的算法还有优化的必要,如果有朋友可以指点一二,不胜感激

```objectivec
#pragma mark - 创建日历数组
- (void)creatcalendarArrayWithDate:(NSDate *)date
{
    self.calendarDate = [NSMutableArray new];
    self.chineseCalendarDate = [NSMutableArray new];
    self.chineseCalendarDay = [NSMutableArray new];
    for (NSInteger j = 0; j < 42; j ++) {// 创建空占位数组
        [self.calendarDate addObject:@""];
        [self.chineseCalendarDate addObject:@""];
        [self.chineseCalendarDay addObject:@""];
    }
    // 向前推算日期到本月第一天
    NSDate * firstDay = date;
    self.todayInMonth = self.todayInMonth + self.dayInWeek - 2;// 计算在本月日历上所处的位置
    switch (self.dayInWeek) {// 根据本月第一天是周几,来确定之后的日期替换空占位
        case 1:// 周日
            for (NSInteger i = 1; i <= self.days; i ++) {
                [self.calendarDate replaceObjectAtIndex:i - 1 withObject:@(i)];// 替换公历日期
                for (NSInteger j = 1; j <= self.days; j ++) {// 公历日期
                    // 向后推算至本月末
                    NSInteger hours = (j - 1) * 24;
                    NSDate * date = [NSDate dateWithTimeInterval:hours * 60 * 60 sinceDate:firstDay];
                    NSString * chineseDay = [self calculationChinaCalendarWithDate:date dispalyHoliday:YES];
                    [self.chineseCalendarDate replaceObjectAtIndex:j - 1 withObject:chineseDay];// 替换农历日期
                    NSString * noHoliday = [self calculationChinaCalendarWithDate:date dispalyHoliday:NO];
                    [self.chineseCalendarDay replaceObjectAtIndex:j - 1 withObject:noHoliday];// 替换纯农历日期(无节假日)
                }
            }
            self.isIncreaseHeight = NO;
            break;
            
        case 2:// 周一
            for (NSInteger i = 1; i <= self.days + 1; i ++) {
                if (i >= 2) {
                    [self.calendarDate replaceObjectAtIndex:i - 1 withObject:@(i - 1)];
                    for (NSInteger j = 1; j <= self.days + 1; j ++) {
                        if (j >= 2) {
                            // 向后推算至本月末
                            NSInteger hours = (j - 2) * 24;
                            NSDate * date = [NSDate dateWithTimeInterval:hours * 60 * 60 sinceDate:firstDay];
                            NSString * chineseDay = [self calculationChinaCalendarWithDate:date dispalyHoliday:YES];
                            [self.chineseCalendarDate replaceObjectAtIndex:j - 1 withObject:chineseDay];// 替换农历日期
                            NSString * noHoliday = [self calculationChinaCalendarWithDate:date dispalyHoliday:NO];
                            [self.chineseCalendarDay replaceObjectAtIndex:j - 1 withObject:noHoliday];
                        }
                    }

                }
            }
            self.isIncreaseHeight = NO;
            break;
            
        case 3:// 周二
            for (NSInteger i = 1; i <= self.days + 2; i ++) {
                if (i >= 3) {
                    [self.calendarDate replaceObjectAtIndex:i - 1 withObject:@(i - 2)];
                    for (NSInteger j = 1; j <= self.days + 2; j ++) {
                        if (j >= 3) {
                            // 向后推算至本月末
                            NSInteger hours = (j - 3) * 24;
                            NSDate * date = [NSDate dateWithTimeInterval:hours * 60 * 60 sinceDate:firstDay];
                            NSString * chineseDay = [self calculationChinaCalendarWithDate:date dispalyHoliday:YES];
                            [self.chineseCalendarDate replaceObjectAtIndex:j - 1 withObject:chineseDay];// 替换农历日期
                            NSString * noHoliday = [self calculationChinaCalendarWithDate:date dispalyHoliday:NO];
                            [self.chineseCalendarDay replaceObjectAtIndex:j - 1 withObject:noHoliday];
                        }
                    }

                }
            }
            self.isIncreaseHeight = NO;
            break;
            
        case 4:// 周三
            for (NSInteger i = 1; i <= self.days + 3; i ++) {
                if (i >= 4) {
                    [self.calendarDate replaceObjectAtIndex:i - 1 withObject:@(i - 3)];
                    for (NSInteger j = 1; j <= self.days + 3; j ++) {
                        if (j >= 4) {
                            // 向后推算至本月末
                            NSInteger hours = (j - 4) * 24;
                            NSDate * date = [NSDate dateWithTimeInterval:hours * 60 * 60 sinceDate:firstDay];
                            NSString * chineseDay = [self calculationChinaCalendarWithDate:date dispalyHoliday:YES];
                            [self.chineseCalendarDate replaceObjectAtIndex:j - 1 withObject:chineseDay];// 替换农历日期
                            NSString * noHoliday = [self calculationChinaCalendarWithDate:date dispalyHoliday:NO];
                            [self.chineseCalendarDay replaceObjectAtIndex:j - 1 withObject:noHoliday];
                        }
                    }

                }
            }
            self.isIncreaseHeight = NO;
            break;
            
        case 5:// 周四
            for (NSInteger i = 1; i <= self.days + 4; i ++) {
                if (i >= 5) {
                    [self.calendarDate replaceObjectAtIndex:i - 1 withObject:@(i - 4)];
                    for (NSInteger j = 1; j <= self.days + 4; j ++) {
                        if (j >= 5) {
                            // 向后推算至本月末
                            NSInteger hours = (j - 5) * 24;
                            NSDate * date = [NSDate dateWithTimeInterval:hours * 60 * 60 sinceDate:firstDay];
                            NSString * chineseDay = [self calculationChinaCalendarWithDate:date dispalyHoliday:YES];
                            [self.chineseCalendarDate replaceObjectAtIndex:j - 1 withObject:chineseDay];// 替换农历日期
                            NSString * noHoliday = [self calculationChinaCalendarWithDate:date dispalyHoliday:NO];
                            [self.chineseCalendarDay replaceObjectAtIndex:j - 1 withObject:noHoliday];
                        }
                    }

                }
            }
            self.isIncreaseHeight = NO;
            break;
            
        case 6:// 周五
            for (NSInteger i = 1; i <= self.days + 5; i ++) {
                if (i >= 6) {
                    [self.calendarDate replaceObjectAtIndex:i - 1 withObject:@(i - 5)];
                    for (NSInteger j = 1; j <= self.days + 5; j ++) {
                        if (j >= 6) {
                            // 向后推算至本月末
                            NSInteger hours = (j - 6) * 24;
                            NSDate * date = [NSDate dateWithTimeInterval:hours * 60 * 60 sinceDate:firstDay];
                            NSString * chineseDay = [self calculationChinaCalendarWithDate:date dispalyHoliday:YES];
                            [self.chineseCalendarDate replaceObjectAtIndex:j - 1 withObject:chineseDay];// 替换农历日期
                            NSString * noHoliday = [self calculationChinaCalendarWithDate:date dispalyHoliday:NO];
                            [self.chineseCalendarDay replaceObjectAtIndex:j - 1 withObject:noHoliday];
                        }
                    }

                }
            }
            if (self.days == 31) {// 是否为大月
                self.isIncreaseHeight = YES;
            } else {
                self.isIncreaseHeight = NO;
            }
            break;
            
        case 7:// 周六
            for (NSInteger i = 1; i <= self.days + 6; i ++) {
                if (i >= 7) {
                    [self.calendarDate replaceObjectAtIndex:i - 1 withObject:@(i - 6)];
                    for (NSInteger j = 1; j <= self.days + 6; j ++) {
                        if (j >= 7) {
                            // 向后推算至本月末
                            NSInteger hours = (j - 7) * 24;
                            NSDate * date = [NSDate dateWithTimeInterval:hours * 60 * 60 sinceDate:firstDay];
                            NSString * chineseDay = [self calculationChinaCalendarWithDate:date dispalyHoliday:YES];
                            [self.chineseCalendarDate replaceObjectAtIndex:j - 1 withObject:chineseDay];// 替换农历日期
                            NSString * noHoliday = [self calculationChinaCalendarWithDate:date dispalyHoliday:NO];
                            [self.chineseCalendarDay replaceObjectAtIndex:j - 1 withObject:noHoliday];
                        }
                    }

                }
            }
            self.isIncreaseHeight = YES;
            break;
    }
}
```


- 计算农历日期

由于农历、节假日都是在同一个位置展示,就放到了一个函数里

1.复活节采用了Meeus/Jones/Butcher算法
2.二十四节气采用了积日日计算公式F = 365.242 * (y – 1900) + 6.2 + 15.22 *x - 1.9 * sin(0.262 * x)
####探讨:
这个函数当中24节气的算法在执行当中由于需要对积日进行计算,就需要处理1900-1-0这个基准日的日期转换,由于stringFromDate方法过于耗时,会导致一定的线程卡顿,目前我是将这24个节气根据月份分开来执行,然后使用单例NSDateFormatter来解决这个问题

```objectivec
- (NSDateFormatter *)dateFormatter
{
    if (!_dateFormatter) {
        _dateFormatter = [[NSDateFormatter alloc] init];
        [_dateFormatter setDateFormat:@"yyyy-MM-dd"];
    }
    return _dateFormatter;
}

- (NSDateFormatter *)strDateFormatter
{
    if (!_strDateFormatter) {
        _strDateFormatter = [[NSDateFormatter alloc] init];
        [_strDateFormatter setDateFormat:@"MM-dd"];
    }
    return _strDateFormatter;
}

- (NSDate *)baseDate
{
    if (!_baseDate) {
        _baseDate = [self.dateFormatter dateFromString:@"1900-1-1"];
    }
    return _baseDate;
}
```

```objectivec
#pragma mark - 计算农历日期
- (NSString *)calculationChinaCalendarWithDate:(NSDate *)date dispalyHoliday:(BOOL)display
{
    if (isEmpty(date)) {
        return nil;
    }
    NSArray * chineseYears = @[@"甲子", @"乙丑", @"丙寅", @"丁卯", @"戊辰", @"己巳", @"庚午", @"辛未", @"壬申", @"癸酉", @"甲戌", @"乙亥", @"丙子", @"丁丑", @"戊寅", @"己卯", @"庚辰", @"辛己", @"壬午", @"癸未", @"甲申", @"乙酉", @"丙戌", @"丁亥", @"戊子", @"己丑", @"庚寅", @"辛卯", @"壬辰", @"癸巳", @"甲午", @"乙未", @"丙申", @"丁酉", @"戊戌", @"己亥", @"庚子", @"辛丑", @"壬寅", @"癸丑", @"甲辰", @"乙巳", @"丙午", @"丁未", @"戊申", @"己酉", @"庚戌", @"辛亥", @"壬子", @"癸丑", @"甲寅", @"乙卯", @"丙辰", @"丁巳", @"戊午", @"己未", @"庚申", @"辛酉", @"壬戌", @"癸亥"];
    NSArray * chineseMonths = @[@"正月", @"二月", @"三月", @"四月", @"五月", @"六月", @"七月", @"八月",
                                @"九月", @"十月", @"冬月", @"腊月"];
    NSArray * chineseDays = @[@"初一", @"初二", @"初三", @"初四", @"初五", @"初六", @"初七", @"初八", @"初九", @"初十", @"十一", @"十二", @"十三", @"十四", @"十五", @"十六", @"十七", @"十八", @"十九", @"廿十", @"廿一", @"廿二", @"廿三", @"廿四", @"廿五", @"廿六", @"廿七", @"廿八", @"廿九", @"三十"];
    
    NSCalendar * localeCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierChinese];
    
    unsigned unitFlags = NSCalendarUnitYear | NSCalendarUnitMonth |  NSCalendarUnitDay;
    
    NSDateComponents * localeComp = [localeCalendar components:unitFlags fromDate:date];
    
    self.chineseYear = [chineseYears objectAtIndex:localeComp.year - 1];
    NSString * m_str = [chineseMonths objectAtIndex:localeComp.month - 1];
    self.chineseMonth = m_str;
    NSString * d_str = [chineseDays objectAtIndex:localeComp.day - 1];

    NSString * chineseCal_str = d_str;
    
    // 农历节日
    if([chineseMonths containsObject:m_str] && [d_str isEqualToString:@"初一"]) {
        chineseCal_str = m_str;
        if ([m_str isEqualToString:@"正月"] && [d_str isEqualToString:@"初一"]) {
            chineseCal_str = @"春节";
        } else{
            chineseCal_str = @"初一";
        }
    } else if ([m_str isEqualToString:@"正月"] && [d_str isEqualToString:@"十五"]) {
        chineseCal_str = @"元宵节";
    } else if ([m_str isEqualToString:@"五月"] && [d_str isEqualToString:@"初五"]) {
        chineseCal_str = @"端午节";
    } else if ([m_str isEqualToString:@"七月"] && [d_str isEqualToString:@"初七"]) {
        chineseCal_str = @"七夕";
    } else if ([m_str isEqualToString:@"七月"] && [d_str isEqualToString:@"十五"]) {
        chineseCal_str = @"中元节";
    } else if ([m_str isEqualToString:@"八月"] && [d_str isEqualToString:@"十五"]) {
        chineseCal_str = @"中秋节";
    } else if ([m_str isEqualToString:@"九月"] && [d_str isEqualToString:@"初九"]) {
        chineseCal_str = @"重阳节";
    } else if ([m_str isEqualToString:@"腊月"] && [d_str isEqualToString:@"初八"]) {
        chineseCal_str = @"腊八节";
    } else if ([m_str isEqualToString:@"腊月"] && [d_str isEqualToString:@"廿三"]) {
        chineseCal_str = @"小年";
    } else if ([m_str isEqualToString:@"腊月"] && [d_str isEqualToString:@"三十"]) {
        chineseCal_str = @"除夕";
    }
    // 公历节日
    NSDictionary * Holidays = @{@"01-01":@"元旦",
                                @"02-14":@"情人节",
                                @"03-08":@"妇女节",
                                @"03-12":@"植树节",
                                @"04-01":@"愚人节",
                                @"05-01":@"劳动节",
                                @"05-04":@"青年节",
                                @"06-01":@"儿童节",
                                @"07-01":@"建党节",
                                @"08-01":@"建军节",
                                @"09-10":@"教师节",
                                @"10-01":@"国庆节",
                                @"12-24":@"平安夜",
                                @"12-25":@"圣诞节"};
    
//    NSDateFormatter * dateFormatt= [[NSDateFormatter alloc] init];
//    [dateFormatt setDateFormat:@"MM-dd"];
    NSString * nowStr = [self.strDateFormatter stringFromDate:date];
    // 复活节, Meeus/Jones/Butcher算法
    NSUInteger a = self.year % 19;
    NSUInteger b = self.year / 100;
    NSUInteger c = self.year % 100;
    NSUInteger d = b / 4;
    NSUInteger e = b % 4;
    NSUInteger f = (b + 8) / 25;
    NSUInteger g = (b - f + 1) / 3;
    NSUInteger h = (19 * a + b - d - g + 15) % 30;
    NSUInteger i = c / 4;
    NSUInteger k = c % 4;
    NSUInteger l = (32 + (2 * e) + (2 * i) - h - k) % 7;
    NSUInteger m = (a + (11 * h) + (22 * l)) / 451;
    NSUInteger theMonth = (h + l - (7 * m) + 114) / 31;
    NSUInteger day = ((h + l - (7 * m) + 114) % 31)+ 1;
    NSString * easter = [NSString stringWithFormat:@"0%@-%@", @(theMonth), @(day)];
    if ([easter isEqualToString:nowStr]) {
        chineseCal_str = @"复活节";
    }
    
    NSArray * array = [Holidays allKeys];
    if([array containsObject:nowStr]) {
        chineseCal_str = [Holidays objectForKey:nowStr];
    }
    
    // 公历礼拜节日
    NSCalendar * calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
    NSDateComponents * comps = [[NSDateComponents alloc] init];
    NSInteger unit = NSCalendarUnitDay | NSCalendarUnitWeekday | NSCalendarUnitMonth | NSCalendarUnitYear;
    comps = [calendar components:unit fromDate:date];
    NSUInteger month = [comps month];
    NSUInteger dayInMonth = [comps day];
    switch (month) {
        case 5:
            if (dayInMonth == 14) {
                chineseCal_str = @"母亲节";
            }
            break;
        case 6:
            if (dayInMonth == 21) {
                chineseCal_str = @"父亲节";
            }
            break;
        case 11:
            if (dayInMonth == 26) {
                chineseCal_str = @"感恩节";
            }
            break;

            
        default:
            break;
    }
    
    // 二十四节气, 将节气按月份拆开计算,否则由于计算积日所需日期转换stringFromDate方法过于耗时将会造成线程卡顿
    NSString * solarTerms = @"";
    switch (self.month) {// 过滤月份
        case 1:
            for (NSInteger i = 0; i < 2; i ++) {
                solarTerms = [self calculationSolarTermsWithYear:self.year solarTermsIndex:i];
                switch (i) {
                    case 0:
                        if ([solarTerms isEqualToString:nowStr]) {
                            chineseCal_str = @"小寒";
                        }
                        break;
                    case 1:
                        if ([solarTerms isEqualToString:nowStr]) {
                            chineseCal_str = @"大寒";
                        }
                        break;
                }
            }
            break;
        case 2:
            for (NSInteger i = 2; i < 4; i ++) {
                solarTerms = [self calculationSolarTermsWithYear:self.year solarTermsIndex:i];
                switch (i) {
                    case 2:
                        if ([solarTerms isEqualToString:nowStr]) {
                            chineseCal_str = @"立春";
                        }
                        break;
                    case 3:
                        if ([solarTerms isEqualToString:nowStr]) {
                            chineseCal_str = @"雨水";
                        }
                        break;
                }
            }
            break;
        case 3:
            for (NSInteger i = 4; i < 6; i ++) {
                solarTerms = [self calculationSolarTermsWithYear:self.year solarTermsIndex:i];
                switch (i) {
                    case 4:
                        if ([solarTerms isEqualToString:nowStr]) {
                            chineseCal_str = @"惊蛰";
                        }
                        break;
                    case 5:
                        if ([solarTerms isEqualToString:nowStr]) {
                            chineseCal_str = @"春分";
                        }
                        break;
                }
            }
            break;
        case 4:
            for (NSInteger i = 6; i < 8; i ++) {
                solarTerms = [self calculationSolarTermsWithYear:self.year solarTermsIndex:i];
                switch (i) {
                    case 6:
                        if ([solarTerms isEqualToString:nowStr]) {
                            chineseCal_str = @"清明";
                        }
                        break;
                    case 7:
                        if ([solarTerms isEqualToString:nowStr]) {
                            chineseCal_str = @"谷雨";
                        }
                        break;
                }
            }
            break;
        case 5:
            for (NSInteger i = 8; i < 10; i ++) {
                solarTerms = [self calculationSolarTermsWithYear:self.year solarTermsIndex:i];
                switch (i) {
                    case 8:
                        if ([solarTerms isEqualToString:nowStr]) {
                            chineseCal_str = @"立夏";
                        }
                        break;
                    case 9:
                        if ([solarTerms isEqualToString:nowStr]) {
                            chineseCal_str = @"小满";
                        }
                        break;
                }
            }
            break;
        case 6:
            for (NSInteger i = 10; i < 12; i ++) {
                solarTerms = [self calculationSolarTermsWithYear:self.year solarTermsIndex:i];
                switch (i) {
                    case 10:
                        if ([solarTerms isEqualToString:nowStr]) {
                            chineseCal_str = @"芒种";
                        }
                        break;
                    case 11:
                        if ([solarTerms isEqualToString:nowStr]) {
                            chineseCal_str = @"夏至";
                        }
                }
            }
            break;
        case 7:
            for (NSInteger i = 12; i < 14; i ++) {
                solarTerms = [self calculationSolarTermsWithYear:self.year solarTermsIndex:i];
                switch (i) {
                    case 12:
                        if ([solarTerms isEqualToString:nowStr]) {
                            chineseCal_str = @"小暑";
                        }
                        break;
                    case 13:
                        if ([solarTerms isEqualToString:nowStr]) {
                            chineseCal_str = @"大暑";
                        }
                    break;                }
            }
            break;
        case 8:
            for (NSInteger i = 14; i < 16; i ++) {
                solarTerms = [self calculationSolarTermsWithYear:self.year solarTermsIndex:i];
                switch (i) {
                    case 14:
                        if ([solarTerms isEqualToString:nowStr]) {
                            chineseCal_str = @"立秋";
                        }
                        break;
                    case 15:
                        if ([solarTerms isEqualToString:nowStr]) {
                            chineseCal_str = @"处暑";
                        }
                        break;
                }
            }
            break;
        case 9:
            for (NSInteger i = 16; i < 18; i ++) {
                solarTerms = [self calculationSolarTermsWithYear:self.year solarTermsIndex:i];
                switch (i) {
                    case 16:
                        if ([solarTerms isEqualToString:nowStr]) {
                            chineseCal_str = @"白露";
                        }
                        break;
                    case 17:
                        if ([solarTerms isEqualToString:nowStr]) {
                            chineseCal_str = @"秋分";
                        }
                        break;
                }
            }
            break;
        case 10:
            for (NSInteger i = 18; i < 20; i ++) {
                solarTerms = [self calculationSolarTermsWithYear:self.year solarTermsIndex:i];
                switch (i) {
                    case 18:
                        if ([solarTerms isEqualToString:nowStr]) {
                            chineseCal_str = @"寒露";
                        }
                        break;
                    case 19:
                        if ([solarTerms isEqualToString:nowStr]) {
                            chineseCal_str = @"霜降";
                        }
                        break;
                }
            }
            break;
        case 11:
            for (NSInteger i = 20; i < 22; i ++) {
                solarTerms = [self calculationSolarTermsWithYear:self.year solarTermsIndex:i];
                switch (i) {
                    case 20:
                        if ([solarTerms isEqualToString:nowStr]) {
                            chineseCal_str = @"立冬";
                        }
                        break;
                    case 21:
                        if ([solarTerms isEqualToString:nowStr]) {
                            chineseCal_str = @"小雪";
                        }
                    break;
                }
            }
            break;
        case 12:
            for (NSInteger i = 22; i < 24; i ++) {
                solarTerms = [self calculationSolarTermsWithYear:self.year solarTermsIndex:i];
                switch (i) {
                    case 22:
                        if ([solarTerms isEqualToString:nowStr]) {
                            chineseCal_str = @"大雪";
                        }
                        break;
                    case 23:
                        if ([solarTerms isEqualToString:nowStr]) {
                            chineseCal_str = @"冬至";
                        }
                        break;
                }
            }
            break;
    }
    
    if (display == YES) {// 需要显示假期&节日
        return chineseCal_str;
    }
    return d_str;
}
```


- 计算24节气的具体日期

这里的计算是整个线程里最耗时的地方,昨天用instruments查看这里的执行,竟然有8000x,我想最可能到这这个的原因就是dateFromString这里了,在我做了一些优化调整后,虽然已经不卡顿了,但不知道有什么更好的解决方案吗?

```objectivec
#pragma mark - 计算二十四节气的具体日期
/**
 * @param year 年份
 * @param index 节气索引,0代表小寒,1代表大寒,其它节气按照顺序类推
 */
- (NSString *)calculationSolarTermsWithYear:(NSUInteger)year solarTermsIndex:(NSUInteger)index
{
    NSString * solarTerms = @"";
    CGFloat base = 365.242 * (year - 1900) + 6.2 + (15.22 * index) - (1.9 * sinf(0.262 * index));// 计算积日
    NSInteger hours = (base - 1) * 24;// 由于基准日为1900年1月0日,所以这里需要-1
    NSDate * date = [NSDate dateWithTimeInterval:hours * 60 * 60 sinceDate:self.baseDate];
   
    solarTerms = [self.strDateFormatter stringFromDate:date];
    
    
    return solarTerms;
}```

### 2.3 动画
- 动画方面主要就是两个方面,`翻页动画`和`点击效果`


- 翻页动画

```objectivec
+ (void)animationWithView:(UIView *)view andEffect:(SK_ANIMATION)effect isNext:(BOOL)next
{
    CATransition * transition = [CATransition animation];
    if (next == YES) {// 向下翻页
        switch (effect) {
            case SK_ANIMATION_REVEAL:
                transition.type = @"pageUnCurl";
                transition.subtype = kCATransitionFromLeft;
                break;
            case SK_ANIMATION_RIPPLE:
                transition.type = @"rippleEffect";
                transition.subtype = kCATransitionFromLeft;
                break;
            case SK_ANIMATION_SUCK:
                transition.type = @"suckEffect";
                transition.subtype = kCATransitionFromLeft;
                break;
        }
    } else {
        switch (effect) {
            case SK_ANIMATION_REVEAL:
                transition.type = @"pageCurl";
                transition.subtype = kCATransitionFromLeft;
                
                break;
            case SK_ANIMATION_RIPPLE:
                transition.type = @"rippleEffect";
                transition.subtype = kCATransitionFromRight;
                break;
            case SK_ANIMATION_SUCK:
                transition.type = @"suckEffect";
                transition.subtype = kCATransitionFromRight;
                break;
        }
    }
    transition.duration = 0.5;
    [view.layer addAnimation:transition forKey:nil];
    
}
```


- 点击效果

```objectivec
+ (void)clickEffectAnimationForView:(UIView *)view
{
    CABasicAnimation * scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
    scaleAnimation.fromValue = [NSNumber numberWithFloat:1.3];
    scaleAnimation.toValue = [NSNumber numberWithFloat:0.7];
    scaleAnimation.duration = 0.1;
    scaleAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    [view.layer addAnimation:scaleAnimation forKey:nil];
}
```



##### 好了,以上就是本次内容的分享,如果能帮到你,我很开心,欢迎在文章下面留言,在文中提到的关于算法上的优化,希望能够得到大神的指点


# 感谢你花时间阅读以上内容, 如果这个项目能够帮助到你,记得告诉我

Email: [email protected]

你可能感兴趣的:(iOS高可控性日历基础组件-SKCalendarView的使用和实现思路的分享)