簡述
SKCalendarView是一個高可控性的日歷基礎組件,為了提高應用的自由度,默認只提供了日歷部分的視圖封裝,但不涵蓋切換月份按鈕、年月分顯示等非關鍵性控件,但請不要擔心,SKCalendarView為你提供了多樣性的API,你可以很輕松的拿到這些信息去展示在你自己的自定義控件中,以及對當前UI的修改:如:替換日歷主題圖片、節假日或特殊日期的日歷背景、各種現實顏色等等。不僅如此,SKCalendarView還為你封裝了公歷、農歷、節假日以及中國24節氣的核心算法,即使你覺得默認的視圖並不合胃口,也可以直接快速的利用這套算法創造出一個全新的日歷控件。最後,SKCalendarView還提供了一些簡單的切換動畫,如果你不喜歡它,可以忽略掉,用自己的,這裡完全不會受到任何限制。如果覺得還不錯,點個star吧~
效果圖
最新更新:
發布了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後,效果就如同背景圖一樣
// 周 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 = 2) { [self.calendarDate replaceObjectAtIndex:i - 1 withObject:@(i - 1)]; for (NSInteger j = 1; 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 = 3) { [self.calendarDate replaceObjectAtIndex:i - 1 withObject:@(i - 2)]; for (NSInteger j = 1; 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 = 4) { [self.calendarDate replaceObjectAtIndex:i - 1 withObject:@(i - 3)]; for (NSInteger j = 1; 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 = 5) { [self.calendarDate replaceObjectAtIndex:i - 1 withObject:@(i - 4)]; for (NSInteger j = 1; 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 = 6) { [self.calendarDate replaceObjectAtIndex:i - 1 withObject:@(i - 5)]; for (NSInteger j = 1; 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 = 7) { [self.calendarDate replaceObjectAtIndex:i - 1 withObject:@(i - 6)]; for (NSInteger j = 1; 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來解決這個問題
- (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; }
#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這裡了,在我做了一些優化調整後,雖然已經不卡頓了,但不知道有什麼更好的解決方案嗎?
#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 動畫
動畫方面主要就是兩個方面,翻頁動畫和點擊效果
翻頁動畫
+ (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: [email protected]