前段时间项目中牵涉到一个日历控件的自定义实现,其样貌如下图所示。
UI图设计由于项目中使用到这个日历的地方还有几个,且下面不一定有取消确定按钮这些,想来想去还是自己先封装一个整体的日历控件的好,说干就干,封装的主要包含日历头部的左右翻页按钮,中间月份显示以及日历主体部分,至于其他的取消确定按钮这些都可以二次封装或者固定在页面上等等均可,视情况而定咯。
日历上面的头部简单不作说明,主要来说一下主体部分的实现思路。
主体部分分为周日到周六七天横排和下面当前月份的所有天数排列,有的月份是28天,有的是30天,还有的是31天,所以可以知道的是日历主题部分的高度是不固定的,如果是28天,只需要排列4行就可以排列完(假设第一天是周日),再假设当月有31天,第一天周六,那么久需要6行来排列。
根据日历主体的特性,我会使用UICollectionView来实现当前月份中所有天数的排列。collectionView要分为两组,第一组用来显示日 一 二 三 四 五 六
的排列;第二组即为一个个的小方格子(或许不方),用来显示日。为了简便或者是个人编程习惯问题,习惯用xib来搞,那么下面就来展示一下xib布局的效果(两个collectionCell):
接下来就是要创建自定义view了,这个view我也是使用xib来实现的,xib布局图如下:
HmSearchCalendar.xib既然是日历,在view初始化的时候你就要知道当前日期(即今天的年份、月份、日期),同时你也要知道当前月份一共有多少天,本月的第一天是周几,上个月在本月应该显示多少个等这些基本的数据。以下为声明的成员变量(注释也很清楚):
@property (nonatomic, assign) int totalDayThisMonth; // 本月总天数
@property (nonatomic, assign) int firstWeekDay; // 本月第一天周几
@property (nonatomic, assign) int lastSum; // = firstWeekDay 显示上个月格子数
@property (nonatomic, assign) int sumDays; // = totalDayThisMonth + lastSum + nextSum 显示总格子数
@property (nonatomic, assign) int currentShowYear; // 当前选择的年份
@property (nonatomic, assign) int currentShowMonth; // 当前选择的月份
@property (nonatomic, assign) int nowYears; // 今天所属年份
@property (nonatomic, assign) int nowMonth; // 今天所属月份
@property (nonatomic, assign) int nowDay; // 今天
@property (nonatomic, assign) int selectCheckInYear; // 选择入住年份
@property (nonatomic, assign) int selectCheckInMonth; // 选择入住月份
@property (nonatomic, assign) int selectCheckInDay; // 选择入住天 默认当天
@property (nonatomic, assign) int selectCheckOutYear; // 选择退房年份
@property (nonatomic, assign) int selectCheckOutMonth; // 选择退房月份
@property (nonatomic, assign) int selectCheckOutDay; // 选择退房天 默认当天的下一天
@property (nonatomic, assign) int selectCheckInOrOut; // 选择入住或退房 0:选择结束(再点击选择为入住) 1:入住选择结束(再点击选择为退房) 默认0
@property (nonatomic, strong) NSDateComponents *comps;
@property (nonatomic, strong) NSCalendar *calendar;
@property (nonatomic, strong) NSDateFormatter *formatter;
@property (nonatomic, strong) HmSearchCalendarHeaderCollectionCell *cellHeader;
@property (nonatomic, strong) HmSearchCalendarBodyCollectionCell *cellBody;
在view初始化的时候,要遵循collectionView
代理,并初始化成员变量的值。然后根据具体的值来排列布局view,除此之外,还要区分过去时间和未来时间,选择的入住时间和退房时间的先后等问题,详情见代码。
内部实现有些地方使用文字一时半会也说不清,不懂的地方可以查看代码,再者就说说对外暴露的方法和成员变量吧
@property (nonatomic, copy) void (^selectDateSuccess)(NSString *startDate, NSString *endDate, int nightCount);
@property (nonatomic, copy) void (^setNormalValue)(NSString *startDate, NSString *endDate, int nightCount);
@property (nonatomic, copy) void (^collectionViewHeightChanged)(CGFloat height);
@property (nonatomic, strong) NSString *startDate; // 默认入住日期
@property (nonatomic, strong) NSString *endDate; // 默认离店日期
/// 整体背景颜色(默认reb:240,240,240)
@property (nonatomic, strong) UIColor *backColor;
以上代码为在.h
文件中声明的block
和成员变量等,根据名称也能判断出个大概:
selectDateSuccess
block主要就是在用户选择了入住日期和退房日期后返回的开始时间和结束时间以及一共多少个晚上。
setNormalValue
block主要是在view刚刚初始化之后返回默认的开始时间和结束时间以及一共多少个晚上(默认是当天入住,明天退房,共一个晚上;不过有时候需要在初始化的时候入住时间和退房时间是指定的日期,设置默认返回主要是为了在外界显示所用)。
collectionViewHeightChanged
block主要就是返回当前日历view的高度。
startDate
设置默认显示的开始时间,不设置即为今天
endDate
设置默认显示的结束时间,不设置即为明天
backColor
这是背景颜色
类似于backColor
这样的成员变量可以在用到的时候自行定义,我这里只是设置了一个背景色,其他的没有具体设置。
至于使用那就更简单咯
- 引入头文件
- 初始化
- 添加在view上并实现block即可
示例代码如下:
self.calendarV = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([HmSearchCalendar class]) owner:self options:nil].lastObject;
_calendarV.frame = CGRectMake(20, 100, kScreenWidth - 40, 300); // 高度300为随意设置,在初始化后会返回高度再次更新
Weak(self);
_calendarV.setNormalValue = ^(NSString *startDate, NSString *endDate, int nightCount) {
NSLog(@"默认的入住日期是:%@,退房日期是:%@,共%d晚", startDate, endDate, nightCount);
};
_calendarV.selectDateSuccess = ^(NSString *startDate, NSString *endDate, int nightCount) {
NSLog(@"选中的入住日期是:%@,退房日期是:%@,共%d晚", startDate, endDate, nightCount);
};
_calendarV.collectionViewHeightChanged = ^(CGFloat height) {
NSLog(@"我收到通知回调了,高度是:%f", height);
wself.calendarV.hm_height = height;
};
[self.view addSubview:_calendarV];
效果图跟UI一样,我就不再截图了,如有需要的小伙伴可以下载代码使用。
代码传送门
网友评论