近期需要寫一個交互有點DT的日歷控件,具體交互細節這裡略過不表。
不過再怎麼復雜的控件,也是由基礎的零配件組裝起來的,這裡最基本的就是日歷控件。
先上圖:

從圖中可以看出日歷控件就是由一個個小方塊組成的,每一行有7個小方塊,分別表示一周的星期天到星期六。
給定一個月份,我們首先需要知道這個月有多少周。那麼如何確定一個月有多少周呢?
我是這麼想的,在NSDate上做擴展:
@interface NSDate (WQCalendarLogic)
0. 首先需要知道這個月有多少天:
- (NSUInteger)numberOfDaysInCurrentMonth
{
// 頻繁調用 [NSCalendar currentCalendar] 可能存在性能問題
return [[NSCalendar currentCalendar] rangeOfUnit:NSDayCalendarUnit inUnit:NSMonthCalendarUnit forDate:self].length;
}1. 確定這個月的第一天是星期幾。這樣就能知道給定月份的第一周有幾天:
- (NSDate *)firstDayOfCurrentMonth
{
NSDate *startDate = nil;
BOOL ok = [[NSCalendar currentCalendar] rangeOfUnit:NSMonthCalendarUnit startDate:&startDate interval:NULL forDate:self];
NSAssert1(ok, @"Failed to calculate the first day of the month based on %@", self);
return startDate;
}
- (NSUInteger)weeklyOrdinality
{
return [[NSCalendar currentCalendar] ordinalityOfUnit:NSDayCalendarUnit inUnit:NSWeekCalendarUnit forDate:self];
}2. 減去第一周的天數,剩余天數除以7,得到倍數和余數:
- (NSUInteger)numberOfWeeksInCurrentMonth
{
NSUInteger weekday = [[self firstDayOfCurrentMonth] weeklyOrdinality];
NSUInteger days = [self numberOfDaysInCurrentMonth];
NSUInteger weeks = 0;
if (weekday > 1) {
weeks += 1, days -= (7 - weekday + 1);
}
weeks += days / 7;
weeks += (days % 7 > 0) ? 1 : 0;
return weeks;
}@interface WQCalendarTileView : UIView
@interface WQCalendarGridView : UIView @property (nonatomic, weak) iddataSource; @property (nonatomic, weak) id delegate; - (void)reloadData;
@class WQCalendarGridView; @protocol WQCalendarGridViewDataSource@required - (NSUInteger)numberOfRowsInGridView:(WQCalendarGridView *)gridView; - (WQCalendarTileView *)gridView:(WQCalendarGridView *)gridView tileViewForRow:(NSUInteger)row column:(NSUInteger)column; @optional - (CGFloat)heightForRowInGridView:(WQCalendarGridView *)gridView; @end @protocol WQCalendarGridViewDelegate - (void)gridView:(WQCalendarGridView *)gridView didSelectAtRow:(NSUInteger)row column:(NSUInteger)column; @end
第一個dataSource方法上面已經可以計算出來了,第二個dataSource方法需要對每一個tile進行配置,比如非當前月的以灰色展示,那麼我們就需要知道當前月展示中包含的 上個月殘留部分,和 下個月的開頭部分:
#pragma mark - method to calculate days in previous, current and the following month.
- (void)calculateDaysInPreviousMonthWithDate:(NSDate *)date
{
NSUInteger weeklyOrdinality = [[date firstDayOfCurrentMonth] weeklyOrdinality];
NSDate *dayInThePreviousMonth = [date dayInThePreviousMonth];
NSUInteger daysCount = [dayInThePreviousMonth numberOfDaysInCurrentMonth];
NSUInteger partialDaysCount = weeklyOrdinality - 1;
NSDateComponents *components = [dayInThePreviousMonth YMDComponents];
self.daysInPreviousMonth = [NSMutableArray arrayWithCapacity:partialDaysCount];
for (int i = daysCount - partialDaysCount + 1; i < daysCount + 1; ++i) {
WQCalendarDay *calendarDay = [WQCalendarDay calendarDayWithYear:components.year month:components.month day:i];
[self.daysInPreviousMonth addObject:calendarDay];
[self.calendarDays addObject:calendarDay];
}
}
- (void)calculateDaysInCurrentMonthWithDate:(NSDate *)date
{
NSUInteger daysCount = [date numberOfDaysInCurrentMonth];
NSDateComponents *components = [date YMDComponents];
self.daysInCurrentMonth = [NSMutableArray arrayWithCapacity:daysCount];
for (int i = 1; i < daysCount + 1; ++i) {
WQCalendarDay *calendarDay = [WQCalendarDay calendarDayWithYear:components.year month:components.month day:i];
[self.daysInCurrentMonth addObject:calendarDay];
[self.calendarDays addObject:calendarDay];
}
}
- (void)calculateDaysInFollowingMonthWithDate:(NSDate *)date
{
NSUInteger weeklyOrdinality = [[date lastDayOfCurrentMonth] weeklyOrdinality];
if (weeklyOrdinality == 7) return ;
NSUInteger partialDaysCount = 7 - weeklyOrdinality;
NSDateComponents *components = [[date dayInTheFollowingMonth] YMDComponents];
self.daysInFollowingMonth = [NSMutableArray arrayWithCapacity:partialDaysCount];
for (int i = 1; i < partialDaysCount + 1; ++i) {
WQCalendarDay *calendarDay = [WQCalendarDay calendarDayWithYear:components.year month:components.month day:i];
[self.daysInFollowingMonth addObject:calendarDay];
[self.calendarDays addObject:calendarDay];
}
}