首页 > 代码库 > IOS自定义日历控件的简单实现(附思想及过程)
IOS自定义日历控件的简单实现(附思想及过程)
因为程序要求要插入一个日历控件,该空间的要求是从当天开始及以后的六个月内的日历,上网查资料基本上都说只要获取两个条件(当月第一天周几和本月一共有多少天)就可以实现一个简单的日历,剩下的靠自己的简单逻辑就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];
}
}
最后展示很烂的效果图
IOS自定义日历控件的简单实现(附思想及过程)