iOS自定义日历控件的简单实现过程
程序员文章站
2023-12-17 21:38:10
因为程序要求要插入一个日历控件,该空间的要求是从当天开始及以后的六个月内的日历,上网查资料基本上都说只要获取两个条件(当月第一天周几和本月一共有多少天)就可以实现一个简单的...
因为程序要求要插入一个日历控件,该空间的要求是从当天开始及以后的六个月内的日历,上网查资料基本上都说只要获取两个条件(当月第一天周几和本月一共有多少天)就可以实现一个简单的日历,剩下的靠自己的简单逻辑就ok了,下面开始自己从开始到完成的整个过程
1.首先做nsdate类目,扩展一些方法让日期之间转换更加方便
#import <foundation/foundation.h> @interface nsdate (lywcalendar) #pragma mark - 获取日 - (nsinteger)day:(nsdate *)date; #pragma mark - 获取月 - (nsinteger)month:(nsdate *)date; #pragma mark - 获取年 - (nsinteger)year:(nsdate *)date; #pragma mark - 获取当月第一天周几 - (nsinteger)firstweekdayinthismonth:(nsdate *)date; #pragma mark - 获取当前月有多少天 - (nsinteger)totaldaysinmonth:(nsdate *)date; @end
下面一一实现这些方法
#import "nsdate+lywcalendar.h" @implementation nsdate (lywcalendar) /** *实现部分 */ #pragma mark -- 获取日 - (nsinteger)day:(nsdate *)date{ nsdatecomponents *components = [[nscalendar currentcalendar] components:(nscalendarunityear | nscalendarunitmonth | nscalendarunitday) fromdate:date]; return components.day; } #pragma mark -- 获取月 - (nsinteger)month:(nsdate *)date{ nsdatecomponents *components = [[nscalendar currentcalendar] components:(nscalendarunityear | nscalendarunitmonth | nscalendarunitday) fromdate:date]; return components.month; } #pragma mark -- 获取年 - (nsinteger)year:(nsdate *)date{ nsdatecomponents *components = [[nscalendar currentcalendar] components:(nscalendarunityear | nscalendarunitmonth | nscalendarunitday) fromdate:date]; return components.year; } #pragma mark -- 获得当前月份第一天星期几 - (nsinteger)firstweekdayinthismonth:(nsdate *)date{ nscalendar *calendar = [nscalendar currentcalendar]; //设置每周的第一天从周几开始,默认为1,从周日开始 [calendar setfirstweekday:1];//1.sun. 2.mon. 3.thes. 4.wed. 5.thur. 6.fri. 7.sat. nsdatecomponents *comp = [calendar components:(nscalendarunityear | nscalendarunitmonth | nscalendarunitday) fromdate:date]; [comp setday:1]; nsdate *firstdayofmonthdate = [calendar datefromcomponents:comp]; nsuinteger firstweekday = [calendar ordinalityofunit:nscalendarunitweekday inunit:nscalendarunitweekofmonth fordate:firstdayofmonthdate]; //若设置从周日开始算起则需要减一,若从周一开始算起则不需要减 return firstweekday - 1; } #pragma mark -- 获取当前月共有多少天 - (nsinteger)totaldaysinmonth:(nsdate *)date{ nsrange daysinlastmonth = [[nscalendar currentcalendar] rangeofunit:nscalendarunitday inunit:nscalendarunitmonth fordate:date]; return daysinlastmonth.length; }
接下来就要写逻辑部分了,在viewcontroller里面实现,先说思想,首先想到的是用collectionview,但是每个月天数不一样在日历中的显示就不一样,有时候有五行有时候有六行,既然要用自动填充就必须考虑到这一点,下面是代码实现
#import "viewcontroller.h" #import "macro.h" #import "nsdate+lywcalendar.h" #import "lywcollectionviewcell.h" #import "lywcollectionreusableview.h"
定义一些全局变量
static nsstring *cellid = @"cellid"; static nsstring *headerid = @"headerid"; static nsstring *footerid = @"footerid"; @implementation viewcontroller { //自动布局 uicollectionviewflowlayout *_layout; //表格视图 uicollectionview *_collectionview; //当月第一天星期几 nsinteger firstdayinmounthinweekly; nsmutablearray *_firstmounth; //容纳六个数组的数组 nsmutablearray *_sixarray; }
//定义星期视图,若为周末则字体颜色为绿色 self.automaticallyadjustsscrollviewinsets = no;//关闭自动适应 nsarray *weektitlearray = @[@"周日",@"周一",@"周二",@"周三",@"周四",@"周五",@"周六"]; for (int i = 0; i < weektitlearray.count; i++) { uilabel *weektitlelable = [[uilabel alloc]initwithframe:cgrectmake(i * ((screenwidth/(weektitlearray.count))), 64, screenwidth/(weektitlearray.count ), 30)]; if (i == 0 || i == 6) { weektitlelable.textcolor = [uicolor greencolor]; }else{ weektitlelable.textcolor = [uicolor blackcolor]; } weektitlelable.text = [weektitlearray objectatindex:i]; weektitlelable.textalignment = nstextalignmentcenter; [self.view addsubview:weektitlelable]; }
//设置collectionview及自动布局,代理方法尤为重要 _layout = [[uicollectionviewflowlayout alloc]init]; //头部始终在顶端 _layout.sectionheaderspintovisiblebounds = yes; //头部视图高度 _layout.headerreferencesize = cgsizemake(414, 40); _layout.minimumlinespacing = 0; _layout.minimuminteritemspacing = 0; _collectionview = [[uicollectionview alloc]initwithframe:cgrectmake(0, 64 + 30, screenwidth, screenheight - 64 - 30) collectionviewlayout:_layout]; _collectionview.backgroundcolor = [uicolor whitecolor]; //注册表格 [_collectionview registerclass:[lywcollectionviewcell class] forcellwithreuseidentifier:cellid]; //注册头视图 [_collectionview registerclass:[lywcollectionreusableview class] forsupplementaryviewofkind:uicollectionelementkindsectionheader withreuseidentifier:headerid]; //注册尾视图 // [_collectionview registerclass:[uicollectionreusableview class] forcellwithreuseidentifier:footerid]; _collectionview.delegate = self; _collectionview.datasource = self; [self.view addsubview:_collectionview];
逻辑部分,这里有个比较长的三项表达式
(daysinmounth > 29 && (firstdayinthismounth == 6 || firstdayinthismounth ==5) ? 42 : 35)
就是日历到底是六行还是七行,这就要根据日历的特性来判断了,如果当月天数大于29天并且当月第一天星期六(以这个程序的准则)或者星期天是返回六行剩下的返回三行,也有可能返回四行的,但是就这个程序来说是不可能的也就不需要做判断了
//numbermounthes 为宏定义,表示要显示月的个数,程序要求是六个月,所以宏定义为六 //#define numbermounthes 6 //想要展示的月数 //创建六个数组,并将这六个数组装入大数组中 _sixarray = [[nsmutablearray alloc]init]; for (int i = 0; i < numbermounthes ; i++ ) { nsmutablearray *array = [[nsmutablearray alloc]init]; [_sixarray addobject:array]; } //为六个数组写入每个月的日历信息 for (int i = 0 ; i < numbermounthes; i++) { //获取月份 int mounth = ((int)[currentdate month:currentdate] + i)%12; nsdatecomponents *components = [[nsdatecomponents alloc]init]; //获取下个月的年月日信息,并将其转为date components.month = mounth; components.year = 2016 + mounth/12; components.day = 1; nscalendar *calendar = [nscalendar currentcalendar]; nsdate *nextdate = [calendar datefromcomponents:components]; //获取该月第一天星期几 nsinteger firstdayinthismounth = [nextdate firstweekdayinthismonth:nextdate]; //该月的有多少天daysinthismounth nsinteger daysinthismounth = [nextdate totaldaysinmonth:nextdate]; nsstring *string = [[nsstring alloc]init]; for (int j = 0; j < (daysinmounth > 29 && (firstdayinthismounth == 6 || firstdayinthismounth ==5) ? 42 : 35) ; j++) { if (j < firstdayinthismounth || j > daysinthismounth + firstdayinthismounth - 1) { string = @""; [[_sixarray objectatindex:i]addobject:string]; }else{ string = [nsstring stringwithformat:@"%ld",j - firstdayinthismounth + 1]; [[_sixarray objectatindex:i]addobject:string]; } } }
下面是代理方法
//这两个不用说,返回cell个数及section个数 - (nsinteger)collectionview:(uicollectionview *)collectionview numberofitemsinsection:(nsinteger)section{ return [[_sixarray objectatindex:section] count]; } - (nsinteger)numberofsectionsincollectionview:(uicollectionview *)collectionview{ return _sixarray.count; }
//这里是自定义cell,非常简单的自定义 - (uicollectionviewcell *)collectionview:(uicollectionview *)collectionview cellforitematindexpath:(nsindexpath *)indexpath{ lywcollectionviewcell *cell = [collectionview dequeuereusablecellwithreuseidentifier:cellid forindexpath:indexpath]; uiview *blackgroundview = [[uiview alloc]initwithframe:cgrectmake(0, 0, cell.frame.size.width, cell.frame.size.height)]; blackgroundview.backgroundcolor = [uicolor yellowcolor]; cell.datelable.text = [[_sixarray objectatindex:indexpath.section]objectatindex:indexpath.row]; nsdate *date = [[nsdate alloc]init]; nsinteger day = [date day:date]; //设置单击后的颜色 cell.selectedbackgroundview = blackgroundview; return cell; }
自定义cell .h
#import <uikit/uikit.h> @interface lywcollectionviewcell : uicollectionviewcell @property (nonatomic,strong) uilabel *datelable; - (instancetype)initwithframe:(cgrect)frame; @end
.m
#import "lywcollectionviewcell.h" @implementation lywcollectionviewcell - (instancetype)initwithframe:(cgrect)frame{ if (self == [super initwithframe:frame]) { _datelable = [[uilabel alloc] initwithframe:self.bounds]; [_datelable settextalignment:nstextalignmentcenter]; [_datelable setfont:[uifont systemfontofsize:17]]; _datelable.textcolor = [uicolor blackcolor]; [self addsubview:_datelable]; } return self; } @end
接着代理
//cell大小及间距 - (cgsize)collectionview:(uicollectionview *)collectionview layout:(uicollectionviewlayout *)collectionviewlayout sizeforitematindexpath:(nsindexpath *)indexpath{ return cgsizemake(screenwidth/7, screenwidth/7); } - (uiedgeinsets)collectionview:(uicollectionview *)collectionview layout:(uicollectionviewlayout *)collectionviewlayout insetforsectionatindex:(nsinteger)section{ return uiedgeinsetsmake(0, 0, 0, 0); }
既然有日历,总得显示哪年哪月吧,前面已经注册表头视图了,这里只需要实现以下代理方法即可
collectionview有点不同其头视图也有单独的类,和cell一样先自定义headcell,也是非常简单的自定义
.h文件
#import <uikit/uikit.h> @interface lywcollectionreusableview : uicollectionreusableview @property (nonatomic,strong) uilabel *datelable; - (instancetype)initwithframe:(cgrect)frame; @end
.m文件
#import "lywcollectionreusableview.h" @implementation lywcollectionreusableview - (instancetype)initwithframe:(cgrect)frame{ if (self == [super initwithframe:frame]) { _datelable = [[uilabel alloc] initwithframe:self.bounds]; [_datelable settextalignment:nstextalignmentleft]; [_datelable setfont:[uifont systemfontofsize:20]]; _datelable.textcolor = [uicolor blackcolor]; [self addsubview:_datelable]; } return self; } @end
接着代理方法,这里也有个三项判断式,和上面的大同小异,主要是防止12月显示为0月
- (uicollectionreusableview *)collectionview:(uicollectionview *)collectionview viewforsupplementaryelementofkind:(nsstring *)kind atindexpath:(nsindexpath *)indexpath{ if (kind == uicollectionelementkindsectionheader) { lywcollectionreusableview *headerrv = [collectionview dequeuereusablesupplementaryviewofkind:kind withreuseidentifier:headerid forindexpath:indexpath]; //自定义蓝色 headerrv.backgroundcolor = dodger_blue; nsdate *currentdate = [[nsdate alloc]init]; nsinteger year = ([currentdate month:currentdate] + indexpath.section)/12 + 2016; nsinteger mounth = ([currentdate month:currentdate] + indexpath.section) % 12 == 0 ? 12 : ([currentdate month:currentdate] + indexpath.section)%12; headerrv.datelable.text = [nsstring stringwithformat:@"%ld年%ld月",year,mounth]; return headerrv; }else{ return nil; } }
还是代理,处理选中效果,选中的为黄色
- (void)collectionview:(uicollectionview *)collectionview didselectitematindexpath:(nsindexpath *)indexpath{ lywcollectionviewcell *cell = [self collectionview:_collectionview cellforitematindexpath:indexpath]; nsdate *currentdate = [[nsdate alloc]init]; //打印当前日期 if (![cell.datelable.text isequaltostring:@""]) { nsinteger year = ([currentdate month:currentdate] + indexpath.section)/12 + 2016; nsinteger mounth = ([currentdate month:currentdate] + indexpath.section)%12; nsinteger day = [cell.datelable.text intvalue]; nslog(@"%ld年%02ld月%02ld日",year,mounth,day); } //排除空值cell //获取月份 nsinteger mounth = ([currentdate month:currentdate] + indexpath.section) % 12 == 0 ? 12 : ([currentdate month:currentdate] + indexpath.section)%12; nsdatecomponents *components = [[nsdatecomponents alloc]init]; components.month = mounth; components.year = 2016 + mounth/12; components.day = 1; nscalendar *calendar = [nscalendar currentcalendar]; nsdate *nextdate = [calendar datefromcomponents:components]; //获取该月第一天星期几 nsinteger firstdayinthismounth = [nextdate firstweekdayinthismonth:nextdate]; //该月的有多少天daysinthismounth nsinteger daysinthismounth = [nextdate totaldaysinmonth:nextdate]; if ((indexpath.row < firstdayinthismounth || indexpath.row > daysinthismounth + firstdayinthismounth - 1)){ //如果点击空表格则单击无效 [collectionview cellforitematindexpath:indexpath].userinteractionenabled = no; [collectionview reloaddata]; } }
最后展示很烂的效果图:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。