手把手教你编写cocoa Calendar控件之MonthView

手把手教你编写cocoa Calendar控件之MonthView

By   zhai shuai  | 2014/05/12 

手把手教你编写cocoa Calendar控件之MonthView

  项目地址:https://github.com/zhaishuai/CBCalendar

  转载请注明本文章出处:http://www.androiddev.net/

  在上一篇博客手把手教你编写cocoa Calendar控件之DayView(h ttp://www.androiddev.net/)中编者讲述了如何编写最基本的元件DayView,本篇博客将讲述如何编写MonthView。MonthView是由42个DayView的对象构成,结构为6行7列图形界面如下所示:

Screen Shot 2014-05-09 at 5.19.28 PM

如何将这么多的DayView放入MonthView中呢?在NSView中有一个subviews的property,在MonthView中调用 - (NSArray *)subviews 返回的是一个NSArray,因此当我们要设置subviews的时候也要传入一个NSArray的对象。接下来我们实验性的填写以下subviews。首先在项目中新建一个CBMonthView类在CBMonthView.m中添加方法:

- (void) awakeFromNib{
    [super awakeFromNib];
    NSMutableArray *array = [[NSMutableArray alloc] init];
    double width = self.bounds.size.width/7,height = self.bounds.size.height/6;
    for(int i=5,count=1;i!=0;i--){
        for(int j=0;j<7;j++,count++){
            CBDayView *dayView = [[CBDayView alloc] initWithFrame:NSMakeRect(j*width, i*height, width, height)];
            dayView.day = [NSString stringWithFormat:@"%d";,count];
            [array addObject:dayView];
        }
    }
    [self setSubviews:array];
}

恩还有一个地方需要修改,在CBDayView.m中定义initWithFrame:(NSRect)frame方法:

- (id)initWithFrame:(NSRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self initialize];
    }
    return self;
}

 

在MainMenu.xib中像添加CBDayView那样添加CBMonthView(详细参见手把手教你编写cocoa Calendar控件之DayView)之后运行程序我们便得到如下结果:

Screen Shot 2014-05-12 at 8.15.59 PM

样子是出来了但是该视图并不是像日历,别着急接下来我们还要做很多工作让该视图看起来像日历。

要让CBMonthView看起来更像日历那么就要解决给定月份的天数,当前日期(年月日)以及判断给定年月日是周几的问题。为了解决这三个问题笔者使用NSDate、NSDateComponents、NSCalendar这么三个类来解决这三个问题。

1、判断给定月份的天数:

- (int)getMonthLengthWithMonth:(int)month withYear:(int)year{
    NSDateComponents *comps = [[NSDateComponents alloc] init];
    [comps setMonth:month];
    [comps setYear:year];
    NSCalendar *gregorian = [[NSCalendar alloc]
                             initWithCalendarIdentifier:NSGregorianCalendar];
    NSDate *date = [gregorian dateFromComponents:comps];
    NSRange days = [gregorian rangeOfUnit:NSDayCalendarUnit
                                   inUnit:NSMonthCalendarUnit
                                  forDate:date];
    return (int)days.length;
}

2、判断当前日期

- (NSDictionary *)getCurrentMonthDayYear{
    NSCalendar *calendar = [[NSLocale currentLocale] objectForKey:NSLocaleCalendar];
    unsigned unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit |  NSDayCalendarUnit | NSWeekdayCalendarUnit | NSWeekCalendarUnit;
    NSDate *date = [NSDate date];
    NSDateComponents *comps = [calendar components:unitFlags fromDate:date];
    NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
    [dict setObject:[NSNumber numberWithInteger:comps.month] forKey:@"month"];
    [dict setObject:[NSNumber numberWithInteger:comps.day] forKey:@"day"];
    [dict setObject:[NSNumber numberWithInteger:comps.year] forKey:@"year"];
    return dict;
}

3、判断给定日期是周几

- (int)getWeekdayWithMonth:(int)month withDay:(int)day withYear:(int)year{
    NSDateComponents *comps = [[NSDateComponents alloc] init];
    [comps setDay:day];
    [comps setMonth:month];
    [comps setYear:year];
    NSCalendar *gregorian = [[NSCalendar alloc]
                             initWithCalendarIdentifier:NSGregorianCalendar];
    NSDate *date = [gregorian dateFromComponents:comps];
    NSDateComponents *weekdayComponents =
    [gregorian components:NSWeekdayCalendarUnit fromDate:date];
    return (int)[weekdayComponents weekday];
}

为了测试一下三个函数我们在awakeFromNib方法总添加下面语句:

    NSLog(@"%d",[self getMonthLengthWithMonth:5 withYear:2014]);
    NSDictionary *dict = [self getCurrentMonthDayYear];
    NSLog(@"%d年 %d月 %d日",[[dict objectForKey:@"year"] intValue],[[dict objectForKey:@"month"] intValue],[[dict objectForKey:@"day"] intValue]);
    NSLog(@"%d",[self getWeekdayWithMonth:5 withDay:12 withYear:2014]);

得到如下结果:

Screen Shot 2014-05-12 at 8.45.01 PM

好接下来我们要让可爱的CBMonthView显示本月份的天数并标记处本天,我们在CBMonthView.m中添加方法 updateCalendarWithMonth:(int)month withYear:(int)year具体代码如下:

- (void)updateCalendarWithMonth:(int)month withYear:(int)year{
    NSMutableArray *views = [[NSMutableArray alloc] init];
    double height = self.bounds.size.height,width = self.bounds.size.width;
    double w = width/7,h = height/6;
    int firstday = [self getWeekdayWithMonth:month withDay:1 withYear:year];
    int monthLength = [self getMonthLengthWithMonth:month withYear:year];
    NSDictionary *currentDate = [self getCurrentMonthDayYear];
    for(int day = 1,i=5,weekday = firstday;day != monthLength ;i--){
        for(;weekday != 7&&day != monthLength;weekday++,day++){
            CBDayView *dayView = [[CBDayView alloc] initWithFrame:CGRectMake(w*(weekday-1), h*i, w, h)];
            dayView.day = [NSString stringWithFormat:@"%d",day];
            dayView.state = 0;
            if([[currentDate objectForKey:@"day"] intValue]==day&&year==[[currentDate objectForKey:@"year"] intValue]&&month==[[currentDate objectForKey:@"month"] intValue]){
                [dayView drawCirleInRect:YES color:nil];
            }
            [views addObject:dayView];
        }
        weekday = 1;
    }
    [self setSubviews:views];
}

添加完成updateCalendarWithMonth:(int)month withYear:(int)year后我们在改写一下awakeFromNib方法:

- (void) awakeFromNib{
    [super awakeFromNib];
    [self updateCalendarWithMonth:5 withYear:2014];
}

运行后得到下列结果图:
Screen Shot 2014-05-12 at 8.54.40 PM
现在的CBMonthView看下来想那么回事了但是距离我们的最终目标:
Screen Shot 2014-05-09 at 5.19.28 PM
还是有一定的差距。我们最终的CBMonthView中显示了当前月份前一个月和后一个月的信息,为了让我们的CBMonthView达到这样的效果我们还需要在updateCalendarWithMonth:(int)month withYear:(int)yeari添加一些代码:

- (void)updateCalendarWithMonth:(int)month withYear:(int)year{
    NSMutableArray *views = [[NSMutableArray alloc] init];
    double height = self.bounds.size.height,width = self.bounds.size.width;
    double w = width/7,h = height/6;
    int firstday = [self getWeekdayWithMonth:month withDay:1 withYear:year];
    int monthLength = [self getMonthLengthWithMonth:month withYear:year];
    NSDictionary *currentDate = [self getCurrentMonthDayYear];
    for(int day = 1,i=5,weekday = firstday;day <= monthLength ;i--){
        for(;weekday <= 7&&day <= monthLength;weekday++,day++){
            CBDayView *dayView = [[CBDayView alloc] initWithFrame:CGRectMake(w*(weekday-1), h*i, w, h)];
            dayView.day = [NSString stringWithFormat:@"%d",day];
            dayView.state = 0;
            if([[currentDate objectForKey:@"day"] intValue]==day&&year==[[currentDate objectForKey:@"year"] intValue]&&month==[[currentDate objectForKey:@"month"] intValue]){
                [dayView drawCirleInRect:YES color:nil];
            }
            [views addObject:dayView];
        }
        weekday = 1;
    }
    int perious=0;
    if(month-1<=0)
        perious = [self getMonthLengthWithMonth:1 withYear:year-1]-firstday+2;
    else
        perious = [self getMonthLengthWithMonth:month-1 withYear:year]-firstday+2;

    for(int i=0;i<firstday-1;i++,perious++){
        CBDayView *dayView = [[CBDayView alloc] initWithFrame:CGRectMake(w*i, h*5, w, h)];
        [dayView setDayFontColor:[NSColor colorWithCalibratedRed:184.0/225 green:184.0/225 blue:184.0/225 alpha:1]];
        [dayView setDay:[NSString stringWithFormat:@"%d",perious]];
        dayView.state = -1;
        [views addObject:dayView];
    }
    int nextMonthDays = 43-monthLength-firstday;
    int rawNum = nextMonthDays/7;
    int leastDays = monthLength-(8-firstday);

    if(nextMonthDays%7)
        rawNum++;
    for(int i=4-leastDays/7,count=1,j=leastDays%7;i>=0;i--){
        for(;j<7;j++,count++){
            CBDayView *dayView = [[CBDayView alloc] initWithFrame:CGRectMake(w*j, h*i, w, h)];
            [dayView setDayFontColor:[NSColor colorWithCalibratedRed:184.0/225 green:184.0/225 blue:184.0/225 alpha:1]];
            [dayView setDay:[NSString stringWithFormat:@"%d",count]];
            dayView.state = 1;
            [views addObject:dayView];
        }
        j=0;
    }

    [self setSubviews:views];
}

运行一下:

Screen Shot 2014-05-12 at 9.01.33 PM

我们实现了最终的目标。

接下来完善一下CBMonthView并附上最终的代码:

#import <Cocoa/Cocoa.h>

@interface CBMonthView : NSView

@property (nonatomic, strong)NSDictionary *currentDate;

- (void)updateCalendarWithMonth:(int)month withYear:(int)year;

@end
//CBMonthView.m
#import "CBMonthView.h"
#import "CBDayView.h"

@implementation CBMonthView

- (id)initWithFrame:(NSRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self initialize];
    }
    return self;
}

- (id)init{
    self = [super init];
    if(self){
        [self initialize];
    }
    return self;
}

- (void)awakeFromNib{
    [self initialize];
}

- (void)initialize{
    NSDictionary *currentDate = [self getCurrentMonthDayYear];
    self.currentDate = currentDate;
    [self updateCalendarWithMonth:[[currentDate objectForKey:@"month"] intValue] withYear:[[currentDate objectForKey:@"year"] intValue]];
}

- (void)updateCalendarWithMonth:(int)month withYear:(int)year{
    NSMutableArray *views = [[NSMutableArray alloc] init];
    double height = self.bounds.size.height,width = self.bounds.size.width;
    double w = width/7,h = height/6;
    int firstday = [self getWeekdayWithMonth:month withDay:1 withYear:year];
    int monthLength = [self getMonthLengthWithMonth:month withYear:year];
    NSDictionary *currentDate = [self getCurrentMonthDayYear];
    for(int day = 1,i=5,weekday = firstday;day <= monthLength ;i--){
        for(;weekday <= 7&&day <= monthLength;weekday++,day++){
            CBDayView *dayView = [[CBDayView alloc] initWithFrame:CGRectMake(w*(weekday-1), h*i, w, h)];
            dayView.day = [NSString stringWithFormat:@"%d",day];
            dayView.state = 0;
            if([[currentDate objectForKey:@"day"] intValue]==day&&year==[[currentDate objectForKey:@"year"] intValue]&&month==[[currentDate objectForKey:@"month"] intValue]){
                [dayView drawCirleInRect:YES color:nil];
            }
            [views addObject:dayView];
        }
        weekday = 1;
    }
    int perious=0;
    if(month-1<=0)
        perious = [self getMonthLengthWithMonth:1 withYear:year-1]-firstday+2;
    else
        perious = [self getMonthLengthWithMonth:month-1 withYear:year]-firstday+2;

    for(int i=0;i<firstday-1;i++,perious++){
        CBDayView *dayView = [[CBDayView alloc] initWithFrame:CGRectMake(w*i, h*5, w, h)];
        [dayView setDayFontColor:[NSColor colorWithCalibratedRed:184.0/225 green:184.0/225 blue:184.0/225 alpha:1]];
        [dayView setDay:[NSString stringWithFormat:@"%d",perious]];
        dayView.state = -1;
        [views addObject:dayView];
    }
    int nextMonthDays = 43-monthLength-firstday;
    int rawNum = nextMonthDays/7;
    int leastDays = monthLength-(8-firstday);

    if(nextMonthDays%7)
        rawNum++;
    for(int i=4-leastDays/7,count=1,j=leastDays%7;i>=0;i--){
        for(;j<7;j++,count++){
            CBDayView *dayView = [[CBDayView alloc] initWithFrame:CGRectMake(w*j, h*i, w, h)];
            [dayView setDayFontColor:[NSColor colorWithCalibratedRed:184.0/225 green:184.0/225 blue:184.0/225 alpha:1]];
            [dayView setDay:[NSString stringWithFormat:@"%d",count]];
            dayView.state = 1;
            [views addObject:dayView];
        }
        j=0;
    }

    [self setSubviews:views];
}

- (int)getMonthLengthWithMonth:(int)month withYear:(int)year{
    NSDateComponents *comps = [[NSDateComponents alloc] init];
    [comps setMonth:month];
    [comps setYear:year];
    NSCalendar *gregorian = [[NSCalendar alloc]
                             initWithCalendarIdentifier:NSGregorianCalendar];
    NSDate *date = [gregorian dateFromComponents:comps];
    NSRange days = [gregorian rangeOfUnit:NSDayCalendarUnit
                                   inUnit:NSMonthCalendarUnit
                                  forDate:date];
    return (int)days.length;
}

- (NSDictionary *)getCurrentMonthDayYear{
    NSCalendar *calendar = [[NSLocale currentLocale] objectForKey:NSLocaleCalendar];
    unsigned unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit |  NSDayCalendarUnit | NSWeekdayCalendarUnit | NSWeekCalendarUnit;
    NSDate *date = [NSDate date];
    NSDateComponents *comps = [calendar components:unitFlags fromDate:date];
    NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
    [dict setObject:[NSNumber numberWithInteger:comps.month] forKey:@"month"];
    [dict setObject:[NSNumber numberWithInteger:comps.day] forKey:@"day"];
    [dict setObject:[NSNumber numberWithInteger:comps.year] forKey:@"year"];
    return dict;
}

- (int)getWeekdayWithMonth:(int)month withDay:(int)day withYear:(int)year{
    NSDateComponents *comps = [[NSDateComponents alloc] init];
    [comps setDay:day];
    [comps setMonth:month];
    [comps setYear:year];
    NSCalendar *gregorian = [[NSCalendar alloc]
                             initWithCalendarIdentifier:NSGregorianCalendar];
    NSDate *date = [gregorian dateFromComponents:comps];
    NSDateComponents *weekdayComponents =
    [gregorian components:NSWeekdayCalendarUnit fromDate:date];
    return (int)[weekdayComponents weekday];
}

@end

Screen Shot 2014-05-09 at 5.20.39 PM

本项目下载地址:https://github.com/zhaishuai/CBCalendar

本文出处:码农日记http://www.androiddev.net/

作者:翟帅

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值