在iOS上實現一個簡單的日曆控件

近期須要寫一個交互有點DT的日曆控件,具體交互細節這裏略過不表。佈局

不過再怎麼複雜的控件,也是由基礎的零配件組裝起來的,這裏最基本的就是日曆控件。性能

先上圖:ui

從圖中能夠看出日曆控件就是由一個個小方塊組成的,每一行有7個小方塊,分別表示一週的星期天到星期六。atom

給定一個月份,咱們首先須要知道這個月有多少周。那麼如何肯定一個月有多少周呢?code

我是這麼想的,在NSDate上作擴展:component

@interface NSDate (WQCalendarLogic)

0. 首先須要知道這個月有多少天:接口

- (NSUInteger)numberOfDaysInCurrentMonth
{
    // 頻繁調用 [NSCalendar currentCalendar] 可能存在性能問題
    return [[NSCalendar currentCalendar] rangeOfUnit:NSDayCalendarUnit inUnit:NSMonthCalendarUnit forDate:self].length;
}

1. 肯定這個月的第一天是星期幾。這樣就能知道給定月份的第一週有幾天:ci

- (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,獲得倍數和餘數:it

- (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;
}


到這裏,就能夠知道一個月有多少行多少列,從而計算出須要多少個小方塊來展現:io

@interface WQCalendarTileView : UIView

這些小方塊用一個大方塊來承載:

 

@interface WQCalendarGridView : UIView

@property (nonatomic, weak) id<WQCalendarGridViewDataSource> dataSource;
@property (nonatomic, weak) id<WQCalendarGridViewDelegate> delegate;

- (void)reloadData;

 


和UITableView相似,當WQCalendarGridView調用reloadData接口時,會開始進行佈局。而佈局所須要的信息由dataSource和delegate提供:

@class WQCalendarGridView;

@protocol WQCalendarGridViewDataSource <NSObject>

@required

- (NSUInteger)numberOfRowsInGridView:(WQCalendarGridView *)gridView;

- (WQCalendarTileView *)gridView:(WQCalendarGridView *)gridView tileViewForRow:(NSUInteger)row column:(NSUInteger)column;

@optional

- (CGFloat)heightForRowInGridView:(WQCalendarGridView *)gridView;

@end

@protocol WQCalendarGridViewDelegate <NSObject>

- (void)gridView:(WQCalendarGridView *)gridView didSelectAtRow:(NSUInteger)row column:(NSUInteger)column;

@end


  每一行的高度,heightForRow,我比較傾向於由dataSource提供 :)

 

 

第一個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];
    }
}

 

到此,就能夠順利地展示出給定月份的日曆控件了。最近項目比較忙,隨手記一篇 :)

相關文章
相關標籤/搜索