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

By | 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中添加方法:

[code lang=”objc”]
– (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];
}
[/code]

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

[code lang=”objc”]
– (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self initialize];
}
return self;
}
[/code]

 

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

Screen Shot 2014-05-12 at 8.15.59 PM

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

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

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

[code lang=”objc”]
– (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;
}
[/code]

2、判断当前日期

[code lang=”objc”]
– (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;
}
[/code]

3、判断给定日期是周几

[code lang=”objc”]
– (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];
}
[/code]

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

[code lang=”objc”]
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]);
[/code]

得到如下结果:

Screen Shot 2014-05-12 at 8.45.01 PM

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

[code lang=”objc”]
– (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];
}
[/code]

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

[code lang=”objc”]
– (void) awakeFromNib{
[super awakeFromNib];
[self updateCalendarWithMonth:5 withYear:2014];
}
[/code]

运行后得到下列结果图:
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添加一些代码:

[code lang=”objc”]
– (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];
}

[/code]

运行一下:

Screen Shot 2014-05-12 at 9.01.33 PM

我们实现了最终的目标。

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

[code lang=”objc”]
#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
[/code]

Screen Shot 2014-05-09 at 5.20.39 PM

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

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

作者:翟帅

One thought on “手把手教你编写cocoa Calendar控件之MonthView

发表评论

邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据