zoukankan      html  css  js  c++  java
  • 在iOS上实现一个简单的日历控件

    http://blog.csdn.net/jasonblog/article/details/21977481

    近期需要写一个交互有点DT的日历控件,具体交互细节这里略过不表。

    不过再怎么复杂的控件,也是由基础的零配件组装起来的,这里最基本的就是日历控件。

    先上图:

    从图中可以看出日历控件就是由一个个小方块组成的,每一行有7个小方块,分别表示一周的星期天到星期六。

    给定一个月份,我们首先需要知道这个月有多少周。那么如何确定一个月有多少周呢?

    我是这么想的,在NSDate上做扩展:

    1. @interface NSDate (WQCalendarLogic)  
    @interface NSDate (WQCalendarLogic)

    0. 首先需要知道这个月有多少天:

    1. - (NSUInteger)numberOfDaysInCurrentMonth  
    2. {  
    3.     // 频繁调用 [NSCalendar currentCalendar] 可能存在性能问题  
    4.     return [[NSCalendar currentCalendar] rangeOfUnit:NSDayCalendarUnit inUnit:NSMonthCalendarUnit forDate:self].length;  
    5. }  
    - (NSUInteger)numberOfDaysInCurrentMonth
    {
        // 频繁调用 [NSCalendar currentCalendar] 可能存在性能问题
        return [[NSCalendar currentCalendar] rangeOfUnit:NSDayCalendarUnit inUnit:NSMonthCalendarUnit forDate:self].length;
    }

    1. 确定这个月的第一天是星期几。这样就能知道给定月份的第一周有几天:

    1. - (NSDate *)firstDayOfCurrentMonth  
    2. {  
    3.     NSDate *startDate = nil;  
    4.     BOOL ok = [[NSCalendar currentCalendar] rangeOfUnit:NSMonthCalendarUnit startDate:&startDate interval:NULL forDate:self];  
    5.     NSAssert1(ok, @"Failed to calculate the first day of the month based on %@", self);  
    6.     return startDate;  
    7. }  
    8.   
    9. - (NSUInteger)weeklyOrdinality  
    10. {  
    11.     return [[NSCalendar currentCalendar] ordinalityOfUnit:NSDayCalendarUnit inUnit:NSWeekCalendarUnit forDate:self];  
    12. }  
    - (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,得到倍数和余数:

    1. - (NSUInteger)numberOfWeeksInCurrentMonth  
    2. {  
    3.     NSUInteger weekday = [[self firstDayOfCurrentMonth] weeklyOrdinality];  
    4.       
    5.     NSUInteger days = [self numberOfDaysInCurrentMonth];  
    6.     NSUInteger weeks = 0;  
    7.       
    8.     if (weekday > 1) {  
    9.         weeks += 1, days -= (7 - weekday + 1);  
    10.     }  
    11.       
    12.     weeks += days / 7;  
    13.     weeks += (days % 7 > 0) ? 1 : 0;  
    14.       
    15.     return weeks;  
    16. }  
    - (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;
    }

    到这里,就可以知道一个月有多少行多少列,从而计算出需要多少个小方块来展示:

    1. @interface WQCalendarTileView : UIView  
    @interface WQCalendarTileView : UIView

    这些小方块用一个大方块来承载:

    1. @interface WQCalendarGridView : UIView  
    2.   
    3. @property (nonatomic, weak) id<WQCalendarGridViewDataSource> dataSource;  
    4. @property (nonatomic, weak) id<WQCalendarGridViewDelegate> delegate;  
    5.   
    6. - (void)reloadData;  
    @interface WQCalendarGridView : UIView
    
    @property (nonatomic, weak) id<WQCalendarGridViewDataSource> dataSource;
    @property (nonatomic, weak) id<WQCalendarGridViewDelegate> delegate;
    
    - (void)reloadData;

    和UITableView类似,当WQCalendarGridView调用reloadData接口时,会开始进行布局。而布局所需要的信息由dataSource和delegate提供:

    1. @class WQCalendarGridView;  
    2.   
    3. @protocol WQCalendarGridViewDataSource <NSObject>  
    4.   
    5. @required  
    6.   
    7. - (NSUInteger)numberOfRowsInGridView:(WQCalendarGridView *)gridView;  
    8.   
    9. - (WQCalendarTileView *)gridView:(WQCalendarGridView *)gridView tileViewForRow:(NSUInteger)row column:(NSUInteger)column;  
    10.   
    11. @optional  
    12.   
    13. - (CGFloat)heightForRowInGridView:(WQCalendarGridView *)gridView;  
    14.   
    15. @end  
    16.   
    17. @protocol WQCalendarGridViewDelegate <NSObject>  
    18.   
    19. - (void)gridView:(WQCalendarGridView *)gridView didSelectAtRow:(NSUInteger)row column:(NSUInteger)column;  
    20.   
    21. @end  
    @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进行配置,比如非当前月的以灰色展示,那么我们就需要知道当前月展示中包含的 上个月残留部分,和 下个月的开头部分:

    1. #pragma mark - method to calculate days in previous, current and the following month.  
    2.   
    3. - (void)calculateDaysInPreviousMonthWithDate:(NSDate *)date  
    4. {  
    5.     NSUInteger weeklyOrdinality = [[date firstDayOfCurrentMonth] weeklyOrdinality];  
    6.     NSDate *dayInThePreviousMonth = [date dayInThePreviousMonth];  
    7.       
    8.     NSUInteger daysCount = [dayInThePreviousMonth numberOfDaysInCurrentMonth];  
    9.     NSUInteger partialDaysCount = weeklyOrdinality - 1;  
    10.       
    11.     NSDateComponents *components = [dayInThePreviousMonth YMDComponents];  
    12.       
    13.     self.daysInPreviousMonth = [NSMutableArray arrayWithCapacity:partialDaysCount];  
    14.     for (int i = daysCount - partialDaysCount + 1; i < daysCount + 1; ++i) {  
    15.         WQCalendarDay *calendarDay = [WQCalendarDay calendarDayWithYear:components.year month:components.month day:i];  
    16.         [self.daysInPreviousMonth addObject:calendarDay];  
    17.         [self.calendarDays addObject:calendarDay];  
    18.     }  
    19. }  
    20.   
    21. - (void)calculateDaysInCurrentMonthWithDate:(NSDate *)date  
    22. {  
    23.     NSUInteger daysCount = [date numberOfDaysInCurrentMonth];  
    24.     NSDateComponents *components = [date YMDComponents];  
    25.       
    26.     self.daysInCurrentMonth = [NSMutableArray arrayWithCapacity:daysCount];  
    27.     for (int i = 1; i < daysCount + 1; ++i) {  
    28.         WQCalendarDay *calendarDay = [WQCalendarDay calendarDayWithYear:components.year month:components.month day:i];  
    29.         [self.daysInCurrentMonth addObject:calendarDay];  
    30.         [self.calendarDays addObject:calendarDay];  
    31.     }  
    32. }  
    33.   
    34. - (void)calculateDaysInFollowingMonthWithDate:(NSDate *)date  
    35. {  
    36.     NSUInteger weeklyOrdinality = [[date lastDayOfCurrentMonth] weeklyOrdinality];  
    37.     if (weeklyOrdinality == 7) return ;  
    38.       
    39.     NSUInteger partialDaysCount = 7 - weeklyOrdinality;  
    40.     NSDateComponents *components = [[date dayInTheFollowingMonth] YMDComponents];  
    41.       
    42.     self.daysInFollowingMonth = [NSMutableArray arrayWithCapacity:partialDaysCount];  
    43.     for (int i = 1; i < partialDaysCount + 1; ++i) {  
    44.         WQCalendarDay *calendarDay = [WQCalendarDay calendarDayWithYear:components.year month:components.month day:i];  
    45.         [self.daysInFollowingMonth addObject:calendarDay];  
    46.         [self.calendarDays addObject:calendarDay];  
    47.     }  
    48. }  
    #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];
        }
    }

    到此,就可以顺利地展现出给定月份的日历控件了。最近项目比较忙,随手记一篇 :)

  • 相关阅读:
    Pixysoft.Weblications.Notebooks 开发实录
    Pixysoft.Framework.Noebe.Recovery
    搞一个动态加载dll竟然搞了半天,郁闷。动态加载 卸载 Assembly, Appdomain。
    趁着09年还没有结束,写下我对10年的心愿
    再次出现系统更新失误
    自动备份恢复框架开发小结
    Pixysoft.Framework.Pageflows 页面流开发实录
    代码混淆软件 DotFuscator 非常严重的问题,放在首页通知一下各位。
    20100111 一次非常严重的开发事故
    自己orm框架的一个旷世大BUG!自己都恶心死了。
  • 原文地址:https://www.cnblogs.com/xuejinhui/p/4236307.html
Copyright © 2011-2022 走看看