当前位置: 首页 > 编程日记 > 正文

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

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

简述

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

效果图


一.如何使用

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;// 开启日期圆角

获取点击到的日期

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

- (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.

实现

  • 在基础控件的布局上,我们采取最简便的方式:周和日期我们分别使用了weekCollectionViewcalendarCollectionView这两个UICollectionView来完成

  • 而月份的背景数字monthBackgroundLabel作为最上面一层采用的是UILabel,在设置了其sizeweight后,效果就如同背景图一样

    // 周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中采取了以下的办法:

    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的方法
    [SKCalendarAnimationManage clickEffectAnimationForView:self.baseView];

2.2 日历算法

这一部分算法是整个SKCalendarView最核心的部分
  • SKCalendarManage以单例的模式封装了SKCalendarView全部的核心算法

  • 主要难点在于对个别不定期节日,如复活节的日期的计算等,以及24节气和农历的计算,推荐阅读《算法:计算中国农历》

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

#pragma mark - 查看所选日期所处的月份
- (void)checkThisMonthRecordFromToday:(NSDate *)today
{if (isEmpty(today)) {// 如果没有日期,默认今天today = [NSDate date];}[self calculationThisMonthDays:today];// 计算本月天数[self calculationThisMonthFirstDayInWeek:today];// 计算本月第一天是周几
}
  • 计算本月天数
#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;// 保存天数
}
  • 计算本月第一天是周几
#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];// 创建日历数组
}
  • 创建日历数组(公历、农历)

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

#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个节气根据月份分开来执行,虽然卡顿不明显了,但是还是有那么一点,不知哪位大神有没有更好的优化思路,有的话欢迎留言讨论

#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 = [dateFormatt 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这里了,在我做了一些优化调整后,虽然已经不卡顿了,但不知道有什么更好的解决方案吗?

#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));// 计算积日NSDateFormatter * dateFormatter = [[NSDateFormatter alloc] init];[dateFormatter setDateFormat:@"YYYY-MM-dd"];NSDate * baseDate = [dateFormatter dateFromString:@"1900-1-1"];NSInteger hours = (base - 1) * 24;// 由于基准日为1900年1月0日,所以这里需要-1NSDate * date = [NSDate dateWithTimeInterval:hours * 60 * 60 sinceDate:baseDate];NSDateFormatter * strDateFormatter = [[NSDateFormatter alloc] init];[strDateFormatter setDateFormat:@"MM-dd"];solarTerms = [strDateFormatter stringFromDate:date];return solarTerms;
}

2.3 动画

  • 动画方面主要就是两个方面,翻页动画点击效果
  • 翻页动画
+ (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];}
  • 点击效果
+ (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: shevakuilin@gmail.com

相关文章:

懒加载 字典转模型 自定义cell

1 懒加载: 1> 什么是懒加载? 懒加载又称为延时加载,即在系统调用的时候加载,如果系统不调用则不会加载.所谓的懒加载其实就是重写其 get 方法. 2> 特点:在使用懒加载的时候要先判断该方法是否已经存在,如果不存在则再进行实例化. 3> 优点: 不必将创建对象的方法都…

SQL GROUP BY 语句

合计函数 (比如 SUM) 常常需要添加 GROUP BY 语句。 GROUP BY 语句 GROUP BY 语句用于结合合计函数&#xff0c;根据一个或多个列对结果集进行分组。 SQL GROUP BY 语法 SELECT column_name, aggregate_function(column_name) FROM table_name WHERE column_name operator valu…

docker如何push镜像到docker hub个人的仓库

docker如何push镜像到docker hub个人的仓库 step1——找到本地镜像的ID&#xff1a;docker imagesstep2——登陆Hub&#xff1a;docker login --usernameusername --passwordpassword --emailemailstep3——tag&#xff1a;docker tag <imageID> <namespace>/<…

博客开通第一天,加油

博客开通第一天&#xff0c;加油转载于:https://www.cnblogs.com/tianyang01/p/5499881.html

【iOS 开发】iOS 10.3 如何更换 app 图标

2017-04-06 KyrieXu Cocoa开发者社区iOS 10.3 开放了更换 app 图标的 API&#xff0c;核心方法是下面这个&#xff1a; func setAlternateIconName(_ alternateIconName: String?, completionHandler: ((Error?) -> Void)? nil) 这是官方文档&#xff0c;但是你还需要在…

WordPress qTranslate插件跨站请求伪造漏洞

漏洞名称&#xff1a;WordPress qTranslate插件跨站请求伪造漏洞CNNVD编号&#xff1a;CNNVD-201306-058发布时间&#xff1a;2013-06-07更新时间&#xff1a;2013-06-07危害等级&#xff1a; 漏洞类型&#xff1a;跨站请求伪造威胁类型&#xff1a;远程CVE编号&#xff1a;CV…

ESXi6.5环境搭建(一:VMware Workstations 12 Pro 环境的安装及配置)

实验目的及要求 完成VMware workstations安装&#xff0c;会应用相关操作&#xff1b;完成虚拟机中ESXI6.5平台的安装及网络环境配置&#xff1b;完成VMware vSphere Client 6.0软件在PC端的安装及配置&#xff1b;完成使用浏览器或者VMware vSphere Client 6.0中对ESXI6.5的操…

[vs2008]Visual Studio 2008 SP1添加或删除功能提示查找SQLSysClrTypes.msi文件

前言 今天接到领导布置的一个任务&#xff0c;是之前同事负责的项目。离职了&#xff0c;现在客户有些地方需要修改&#xff0c;由于我之前参与过&#xff0c;就落在我的头上了。 然后我就把代码弄了过来&#xff0c;打开发现其中需要用到水晶报表。&#xff08;我觉得不好用&a…

iOS10.3 的评论系统

作者 xuyafei86 关注 2017.03.30 12:39* 字数 428 阅读 265评论 4喜欢 11iOS10.3 对 App 的评论系统进行了较大的升级。主要在三个方面。 支持 App 内评分 在此之前我们要实现 App 内评分需要使用 SKStoreProductViewController。它只会在 App 内部模态打开在 AppStore 的详情页…

windows 内存泄露和资源泄漏调试

AQTime (有x64、win32的)进行内存泄露和资源泄漏监控http://wenku.baidu.com/view/9aa1c2afdd3383c4bb4cd2c1.html x64下载&#xff1a;http://downlite.net/lp.php?coc&nAutomatedQA.AQTime.v6.21.400.x64.Cracked.WORKING-BRD Windows Leaks Detector&#xff08;好象只…

ESXi6.5环境搭建(二:ESXi 6.5环境的安装及配置)

实验目的及要求 完成VMware workstations安装&#xff0c;会应用相关操作&#xff1b;完成虚拟机中ESXI6.5平台的安装及网络环境配置&#xff1b;完成VMware vSphere Client 6.0软件在PC端的安装及配置&#xff1b;完成使用浏览器或者VMware vSphere Client 6.0中对ESXI6.5的操…

Android自定义ListView的Item无法响应OnItemClick的解决办法

转&#xff1a;如果你的自定义ListViewItem中有Button或者Checkable的子类控件的话&#xff0c;那么默认focus是交给了子控件&#xff0c;而ListView的Item能被选中的基础是它能获取Focus&#xff0c;也就是说我们可以通过将ListView中Item中包含的所有控件的focusable属性设置…

iPA 打包小工具

2017-04-07原文链接&#xff1a;http://icofans.com/2017/04/06/%E6%A1%8C%E9%9D%A2%E6%89%93%E5%8C%85IPA%E5%B0%8F%E7%A8%8B%E5%BA%8F/对项目进行 iPA 打包导出 使用方法&#xff1a;运行后&#xff0c;将项目文件夹拖拽至主界面&#xff0c;此时项目便开始打包。打包完成后会…

ESXi6.5环境搭建(三:vSphere Client6.0安装)

实验目的及要求 完成VMware workstations安装&#xff0c;会应用相关操作&#xff1b;完成虚拟机中ESXI6.5平台的安装及网络环境配置&#xff1b;完成VMware vSphere Client 6.0软件在PC端的安装及配置&#xff1b;完成使用浏览器或者VMware vSphere Client 6.0中对ESXI6.5的操…

JavaScript arguments对象

1、在JavaScript中&#xff0c;arguments对象是比较特别的一个对象&#xff0c;实际上是当前函数的一个内置属性。arguments非常类似Array&#xff0c;但实际上又不是一个Array实例。可以通过如下代码得以证实&#xff08;当然&#xff0c;实际上&#xff0c;在函数funcArg中&a…

iOS开发之 - 好玩的富文本

周末闲着没事&#xff0c;就想着不如把那些容易遗忘的知识点整理一下&#xff0c;一来可以让有需要的朋友少走弯路&#xff0c;二来自己以后再忘记的时候也可以回头看看......但 iOS 中小冷易忘的知识点实在太多了&#xff0c;不知道该从哪里开始整理&#xff0c;“百无聊赖”逛…

sharepoint自带JS函数获取URL参数

GetUrlKeyValue 转载于:https://www.cnblogs.com/bmib/p/3139749.html

ESXi6.5环境搭建(四:虚拟机操作系统安装及配置)

实验目的及要求 完成VMware workstations安装&#xff0c;会应用相关操作&#xff1b;完成虚拟机中ESXI6.5平台的安装及网络环境配置&#xff1b;完成VMware vSphere Client 6.0软件在PC端的安装及配置&#xff1b;完成使用浏览器或者VMware vSphere Client 6.0中对ESXI6.5的操…

iOS 生成带 logo 的二维码,区域截屏保存至相册(小功能二连发 (一))

原文链接&#xff1a;http://www.jianshu.com/p/36e9f012ef39生成带 logo 的二维码 区域截屏相关 —— 由3033分享开篇 最近项目需要搞了几个相对独立的小功能&#xff0c;今天有空总结一下他们的实现思路和方法&#xff0c;并总结一点项目中帮同事解决的问题&#xff0c;在此立…

JavaScript-学习一全局变量

因为局部变量只作用于函数内&#xff0c;所以不同的函数可以使用相同名称的变量。 局部变量在函数开始执行时创建&#xff0c;函数执行完后局部变量会自动销 不限制位置的 JavaScript 变量生命周期在它声明时初始化。 局部变量在函数执行完毕后销毁。 全局变量在页面关闭后销毁…

Android 4.2真坑爹

艹~~~&#xff0c;Android4.2真坑爹&#xff0c;4.1以前的方法都不能使用了。 操蛋呢。。。转载于:https://www.cnblogs.com/liushuibufu/p/3253611.html

ESXi6.5环境搭建(五:常见问题及解决方案实验总结)

实验目的及要求 完成VMware workstations安装&#xff0c;会应用相关操作&#xff1b;完成虚拟机中ESXI6.5平台的安装及网络环境配置&#xff1b;完成VMware vSphere Client 6.0软件在PC端的安装及配置&#xff1b;完成使用浏览器或者VMware vSphere Client 6.0中对ESXI6.5的操…

《Linux4.0设备驱动开发详解》笔记--第十二章:Linux设备驱动的软件架构思想

待补充转载于:https://www.cnblogs.com/zcjboke/p/5513130.html

iOS_Development~ 添加 / 隐藏 UITabBar 右上角的小红点

原文链接&#xff1a;http://www.jianshu.com/p/de72118a49ad添加 / 隐藏 UITabBar 右上角的小红点 —— 由anticipate_91分享添加/隐藏UITabBar右上角的小红点 话不多说&#xff0c;直接上代码吧&#xff01; 1.添加tabBar的小红点 /** 添加tabBar的小红点* index&#xff1…

解决ubuntu上opengl的问题

装完ubuntu之后&#xff0c;对于opengl的程序总是出现问题&#xff0c;先将解决方案列出如下&#xff1a; http://www.linuxforums.org/forum/ubuntu-linux/175490-graphics-driver-problem.html http://superuser.com/questions/484991/nvidia-graphics-driver-in-ubuntu-12-0…

OpenStack环境搭建(一:Virtual Box 5.1 环境的安装及配置)

实验要求&#xff1a; 完成Virtual box平台安装&#xff0c;会应用相关操作&#xff1b;在virtual box虚拟平台上部署Fuel Master节点&#xff1b;在virtual box虚拟平台上部署计算节点Computer&#xff1b;在virtual box虚拟平台上部署控制节点Controller&#xff1b;在web控…

[转载]SSH框架搭建详细图文教程

什么是SSH? SSH对应 struts spring hibernatestruts 采用MVC模式&#xff0c;主要是作用于用户交互spring 采用IOC和AOP~作用比较抽象&#xff0c;是用于项目的松耦合hibernate 是对象持久化框架&#xff0c;其实就是实体类和数据库表建立关系&#xff0c;操作类就会触发相应的…

iOS 开发之 pdf 文档的加载与浏览的 4 种方式

原文链接&#xff1a;http://www.jianshu.com/p/1d4305a02ea5在我们的开发中&#xff0c;有些像电子书类型的 app 的开发会涉及到 pdf 文档的加载与展示。由于笔者项目中正好涉及到这块&#xff0c;于是将 pdf 常用的几种加载方式做个总结。以供后面可能用到的同学做个参考。 —…

利用三个点(trsf)来实现各种规则图形的实现

在Val3,是使用trsf(x,y,z,rx,ry,rz)来实现三维空间点的位置与方向。其中第一点和第二点位置很重要&#xff0c;第三点是用来确定方向。根据这三个点先确定一个用户坐标系。在这个坐标系中&#xff0c;实现圆&#xff0c;三角形&#xff0c;矩形&#xff0c;腰圆&#xff0c;正五…

OpenStack环境搭建(二:Fuel Master的安装及配置)

实验要求&#xff1a; 完成Virtual box平台安装&#xff0c;会应用相关操作&#xff1b;在virtual box虚拟平台上部署Fuel Master节点&#xff1b;在virtual box虚拟平台上部署计算节点Computer&#xff1b;在virtual box虚拟平台上部署控制节点Controller&#xff1b;在web控…