IOS自定义日历控件的简单实现(附思想及过程)

时间:2023-02-26 09:19:32

因为程序要求要插入一个日历控件,该空间的要求是从当天开始及以后的六个月内的日历,上网查资料基本上都说只要获取两个条件(当月第一天周几和本月一共有多少天)就可以实现一个简单的日历,剩下的靠自己的简单逻辑就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.Sun. 2.Mon. 3.Thes. 4.Wed. 5.Thur. 6.Fri. 7.Sat.
NSDateComponents *comp = [calendar components:(NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay) fromDate:date];
[comp setDay:];
NSDate *firstDayOfMonthDate = [calendar dateFromComponents:comp];
NSUInteger firstWeekday = [calendar ordinalityOfUnit:NSCalendarUnitWeekday inUnit:NSCalendarUnitWeekOfMonth forDate:firstDayOfMonthDate];
//若设置从周日开始算起则需要减一,若从周一开始算起则不需要减
return firstWeekday - ;
}
#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 = ; i < weekTitleArray.count; i++) {
UILabel *weekTitleLable = [[UILabel alloc]initWithFrame:CGRectMake(i * ((ScreenWidth/(weekTitleArray.count))), , ScreenWidth/(weekTitleArray.count ), )];
if (i == || i == ) {
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(, );
_layout.minimumLineSpacing = ;
_layout.minimumInteritemSpacing = ;
_collectionView = [[UICollectionView alloc]initWithFrame:CGRectMake(, + , ScreenWidth, ScreenHeight - - ) 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 = ; i < NumberMounthes ; i++ ) {
NSMutableArray *array = [[NSMutableArray alloc]init];
[_sixArray addObject:array];
}
//为六个数组写入每个月的日历信息
for (int i = ; i < NumberMounthes; i++) {
//获取月份
int mounth = ((int)[currentDate month:currentDate] + i)%;
NSDateComponents *components = [[NSDateComponents alloc]init];
//获取下个月的年月日信息,并将其转为date
components.month = mounth;
components.year = + mounth/;
components.day = ;
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 = ; j < (daysInMounth > && (firstDayInThisMounth == || firstDayInThisMounth ==) ? : ) ; j++) {
if (j < firstDayInThisMounth || j > daysInThisMounth + firstDayInThisMounth - ) {
string = @"";
[[_sixArray objectAtIndex:i]addObject:string];
}else{
string = [NSString stringWithFormat:@"%ld",j - firstDayInThisMounth + ];
[[_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(, , 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:]];
_dateLable.textColor = [UIColor blackColor];
[self addSubview:_dateLable];
}
return self;
} @end

接着代理

//cell大小及间距
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
return CGSizeMake(ScreenWidth/, ScreenWidth/);
} - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section{
return UIEdgeInsetsMake(, , , );
}

既然有日历,总得显示哪年哪月吧,前面已经注册表头视图了,这里只需要实现以下代理方法即可

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:]];
_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)/ + ;
NSInteger mounth = ([currentDate month:currentDate] + indexPath.section) % == ? : ([currentDate month:currentDate] + indexPath.section)%;
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];

}

}

最后展示很烂的效果图

IOS自定义日历控件的简单实现(附思想及过程)