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

转载 2015年11月18日 14:56:31

手把手教你编写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/

作者:翟帅

《手把手教你读财报》- 读书总结

本书的特色 以贵州茅台2013年财报为线索,引领全书,读起来不至于太枯燥。 理论+实践,很好的典范! 贵州茅台,业务比较单一,财报比较好理解。...
  • FansUnion
  • FansUnion
  • 2016年08月10日 17:17
  • 2488

手把手教你架构3D游戏引擎

在这里将多年游戏研发经验的积累写成一本书奉献给读者,目前已经开始预售,网址: http://www.broadview.com.cn/article/70 该书主要是将游戏中经常使用的技术给大家做了...
  • jxw167
  • jxw167
  • 2016年12月08日 10:16
  • 5247

手把手教你安卓入门(二)

我们就开始正式开发“计算器”应用
  • anddlecn
  • anddlecn
  • 2016年05月23日 20:59
  • 9678

3D游戏引擎技术架构设计

市面上目前流行最广的游戏引擎,在2D领域是Cocos2d引擎,在3D领域是Unity3D。这二者分别占领2D游戏开发市场和3D游戏开发市场,虽然虚幻4也开始发力,引擎代码开源。但是由于它是C++编写的...
  • jxw167
  • jxw167
  • 2016年12月17日 13:25
  • 2232

手把手教你学Ztree

https://note.wiz.cn/pages/manage/biz/payRead.html?kb=3fe9d146-6498-4882-b75c-f533442aba5b
  • huaweitman
  • huaweitman
  • 2017年06月21日 17:51
  • 675

《手把手教你学C语言》学习笔记(1)---C语言的特点

C语言的两个知识点库和指针
  • guochaoxxl
  • guochaoxxl
  • 2017年04月22日 08:18
  • 707

手把手教你玩转SOCKET模型:完成例程(Completion Routine)篇

本文假设你已经对重叠I/O的机制已有了解,否则请先参考本系列的前一篇《手把手教你玩转SOCKET模型之重叠I/O篇》; 目录: 1.完成例程的优点 2.完成例程的基本原理 3....
  • liujiayu2
  • liujiayu2
  • 2015年06月09日 12:55
  • 847

手把手教你编写一个具有基本功能的shell(已开源)

本文一步一步地教你如何编写一个具有基本功能的shell,特点是由简单到复杂,逐步添加新的特性,注重思路的展示而不是代码的堆砌,因而不会直接呈现一个完善的版本;同时练习相关的Linux的API,有助于破...
  • wuyuegb2312
  • wuyuegb2312
  • 2013年11月03日 13:12
  • 1946

推荐!手把手教你使用Git的详细操作

原文出处:涂根华的博客–推荐!手把手教你使用Git的详细操作 以下是其中的一部分,想了解更多,请参考涂根华的博客 Git基本常用命令: mkdir: XX (创建一个空目录 XX指目录名)pwd...
  • xsj_blog
  • xsj_blog
  • 2016年06月16日 10:54
  • 582

手把手教你学单片机--51单片机C语言开发

手把手教你学单片机--51单片机C语言开发                                                                    ...
  • yu132563
  • yu132563
  • 2016年01月02日 13:56
  • 621
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:手把手教你编写cocoa Calendar控件之MonthView
举报原因:
原因补充:

(最多只允许输入30个字)