iOS 日历组件FSCalendar
项目中有用到日期选择框架,最好是能有日历效果的,为了方便后续拓展可能还需要有标记功能和可拓展功能,于是找到了一个很合适的轮子FSCalendar
效果图
这个日历是放在弹窗中的,点击弹窗日历,可以滑动或者按钮左右移动,可以显示今日日期,可以取消选择,也可以标记今日时间并做一些简单的定制,标记事件这里暂时没有做,有需要可以拓展
这里标记一下使用FSCalendar的一些要点:
1.FSCalendar对象创建需要使用frame的方式,如果使用masonry来布局,你会发现日历可能根本显示不出来或者显示出来的效果特别慢,遇到这种情况,可以通过一种取巧的办法,通过masonry布局一个占位符view,然后在初始化FSCalendar让日历视图的完全覆盖占位符view
UIView *holderView = [UIView new];
[self.containerView addSubview:holderView];
CGFloat height = [[UIDevice currentDevice].model hasPrefix:@"iPad"] ? 450 : 300;
[holderView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.mas_offset(0);
make.top.mas_equalTo(lineView.mas_bottom).mas_offset(0);
make.height.mas_equalTo(height);
make.bottom.mas_offset(0);
make.width.mas_equalTo(SCREEN_WIDTH);
}];
FSCalendar *calendar = [[FSCalendar alloc] initWithFrame:CGRectMake(0, 51, SCREEN_WIDTH, height)];
calendar.dataSource = self;
calendar.delegate = self;
calendar.backgroundColor = [UIColor whiteColor];
calendar.appearance.headerMinimumDissolvedAlpha = 0;
calendar.appearance.caseOptions = FSCalendarCaseOptionsHeaderUsesUpperCase;
calendar.appearance.headerTitleColor = OLColorFromRGB(color_text);
calendar.appearance.headerTitleFont = [UIFont boldSystemFontOfSize:15];
calendar.appearance.weekdayTextColor = OLColorFromRGB(color_text);
calendar.appearance.headerDateFormat = @"yyyy / MM";
calendar.appearance.selectionColor = OLColorFromRGB(color_mainTheme);
calendar.appearance.titleTodayColor = OLColorFromRGB(color_red);
calendar.appearance.subtitleTodayColor = OLColorFromRGB(color_red);
calendar.appearance.todayColor = UIColor.clearColor;
_calendar = calendar;
[self.containerView addSubview:self.calendar];
2.显示左右箭头和今日按钮
左右箭头直接使用按钮覆盖到calendar即可,添加按钮事件,通过点击按钮切换日历page
- (instancetype)init{
if (self = [super init]) {
self.gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
}
return self;
}
- (void)previousClicked:(id)sender{
NSDate *currentMonth = self.calendar.currentPage;
NSDate *previousMonth = [self.gregorian dateByAddingUnit:NSCalendarUnitMonth value:-1 toDate:currentMonth options:0];
[self.calendar setCurrentPage:previousMonth animated:YES];
}
- (void)nextClicked:(id)sender{
NSDate *currentMonth = self.calendar.currentPage;
NSDate *nextMonth = [self.gregorian dateByAddingUnit:NSCalendarUnitMonth value:1 toDate:currentMonth options:0];
[self.calendar setCurrentPage:nextMonth animated:YES];
}
- (void)todayItemClicked:(id)sender{
[self.calendar setCurrentPage:[NSDate date] animated:YES];
}
3.设置字体大小、选中颜色、日期格式等
///年月日日期字体大小,文字颜色,日期格式:2024 / 9
calendar.appearance.headerTitleColor = OLColorFromRGB(color_text);
calendar.appearance.headerTitleFont = [UIFont boldSystemFontOfSize:15];
calendar.appearance.headerDateFormat = @"yyyy / MM";
///周字体颜色: 周一/周二
calendar.appearance.weekdayTextColor = OLColorFromRGB(color_text);
/// 日期选中颜色
calendar.appearance.selectionColor = OLColorFromRGB(color_mainTheme);
calendar.appearance.titleTodayColor = OLColorFromRGB(color_red);
calendar.appearance.subtitleTodayColor = OLColorFromRGB(color_red);
/// 日期选中颜色
calendar.appearance.selectionColor = OLColorFromRGB(color_mainTheme);
calendar.appearance.titleTodayColor = OLColorFromRGB(color_red);
calendar.appearance.subtitleTodayColor = OLColorFromRGB(color_red);
/// 设置今日日期默认背景颜色:不设置默认红
calendar.appearance.todayColor = UIColor.clearColor;
4.今日日期自定义
今日日期默认只显示一个数字,如果要定制一下,显示中文加数字,就需要重写calendar的数据源方法
/// 给日期添加分类
@property (nonatomic, readonly) NSInteger year; ///< Year component
@property (nonatomic, readonly) NSInteger month; ///< Month component (1~12)
@property (nonatomic, readonly) NSInteger day; ///< Day component (1~31)
- (NSInteger)year {
return [[[NSCalendar currentCalendar] components:NSCalendarUnitYear fromDate:self] year];
}
- (NSInteger)month {
return [[[NSCalendar currentCalendar] components:NSCalendarUnitMonth fromDate:self] month];
}
- (NSInteger)day {
return [[[NSCalendar currentCalendar] components:NSCalendarUnitDay fromDate:self] day];
}
- (BOOL)isToday {
if (fabs(self.timeIntervalSinceNow) >= 60 * 60 * 24) return NO;
return [NSDate new].day == self.day;
}
/// 重写calendar数据源方法
- (NSString *)calendar:(FSCalendar *)calendar titleForDate:(NSDate *)date{
if (date.isToday) {
return @(date.day).stringValue;
}
return nil;
}
-(NSString *)calendar:(FSCalendar *)calendar subtitleForDate:(NSDate *)date{
if (date.isToday) {
return @"今日";
}
return nil;
}
5.点击取消选择清空时间选中
-(void)clearSelectDate{
for (NSDate *date in self.calendar.selectedDates) {
[self.calendar deselectDate:date];
}
}
OLCalendarPickerView使用方法:
static NSDate *lastDate;
OLCalendarPickerView *pickerView = [OLCalendarPickerView new];
[pickerView showInView:nil withDate:lastDate complete:^(NSDate * _Nonnull date) {
NSLog(@"%@",date);
lastDate = date;
}];
弹窗源码:
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface OLCalendarPickerView : UIControl
/// 显示弹窗视图
/// - Parameters:
/// - view: 视图显示在哪里:nil则显示在window上
/// - date: 默认显示日期页:nil则显示今日日期页
/// - completeBlock: 完成回调返回date
///
-(void)showInView:(UIView * _Nullable)view
withDate:(NSDate * _Nullable)date
complete:(void(^)(NSDate * _Nullable date))completeBlock;
@end
NS_ASSUME_NONNULL_END
#import "OLCalendarPickerView.h"
#import "FSCalendar.h"
#import "UIImage+XWExt.h"
#import "NSDate+GYZCalendar.h"
@interface OLCalendarPickerView ()<FSCalendarDelegate,FSCalendarDataSource>
@property(nonatomic, strong) UIView * containerView; //内容区域
@property(nonatomic, strong) UIWindow *window;
@property(nonatomic, assign) CGFloat totalHeight;
@property(nonatomic,strong) UIView *lineView;
@property(nonatomic,strong) FSCalendar *calendar;
@property (strong, nonatomic) NSCalendar *gregorian;
@property(nonatomic,strong) UIButton *previousButton;
@property(nonatomic,strong) UIButton *nextButton;
@property(nonatomic,strong) UIButton *todayButton;
/// 清空选择
@property(nonatomic,strong) UIButton *clearButton;
/// 确认选择
@property(nonatomic,strong) UIButton *ensureButton;
@property (nonatomic,copy) void (^completeBlock)(NSDate *date);
@end
@implementation OLCalendarPickerView
- (instancetype)init{
if (self = [super init]) {
self.gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
}
return self;
}
-(instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
//初始化UI
[self setupUI];
}
return self;
}
-(void)setupUI{
[self addTarget:self action:@selector(dismiss) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:self.containerView];
[self.containerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.mas_offset(0);
make.bottom.mas_offset(0);
}];
UILabel *titleLabel = [UILabel new];
titleLabel.text = @"选择查看日期";
titleLabel.font = [UIFont systemFontOfSize:16];
titleLabel.textColor = OLColorFromRGB(color_text);
[self.containerView addSubview:titleLabel];
[titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_offset(10);
make.centerX.mas_offset(0);
make.height.mas_equalTo(30);
}];
[self.containerView addSubview:self.clearButton];
[self.containerView addSubview:self.ensureButton];
[self.clearButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_offset(10);
make.centerY.mas_equalTo(titleLabel);
}];
[self.ensureButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.mas_offset(-10);
make.centerY.mas_equalTo(titleLabel);
}];
UIView *lineView = [UIView new];
lineView.backgroundColor = OLColorFromRGB(color_0xEEEEEE);
self.lineView = lineView;
[self.containerView addSubview:lineView];
[lineView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.mas_offset(0);
make.top.mas_equalTo(titleLabel.mas_bottom).mas_offset(10);
make.height.mas_equalTo(1);
}];
UIView *holderView = [UIView new];
[self.containerView addSubview:holderView];
CGFloat height = [[UIDevice currentDevice].model hasPrefix:@"iPad"] ? 450 : 300;
[holderView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.mas_offset(0);
make.top.mas_equalTo(lineView.mas_bottom).mas_offset(0);
make.height.mas_equalTo(height);
make.bottom.mas_offset(0);
make.width.mas_equalTo(SCREEN_WIDTH);
}];
FSCalendar *calendar = [[FSCalendar alloc] initWithFrame:CGRectMake(0, 51, SCREEN_WIDTH, height)];
calendar.dataSource = self;
calendar.delegate = self;
calendar.backgroundColor = [UIColor whiteColor];
calendar.appearance.headerMinimumDissolvedAlpha = 0;
calendar.appearance.caseOptions = FSCalendarCaseOptionsHeaderUsesUpperCase;
calendar.appearance.headerTitleColor = OLColorFromRGB(color_text);
calendar.appearance.headerTitleFont = [UIFont boldSystemFontOfSize:15];
calendar.appearance.weekdayTextColor = OLColorFromRGB(color_text);
calendar.appearance.headerDateFormat = @"yyyy / MM";
calendar.appearance.selectionColor = OLColorFromRGB(color_mainTheme);
calendar.appearance.titleTodayColor = OLColorFromRGB(color_red);
calendar.appearance.subtitleTodayColor = OLColorFromRGB(color_red);
calendar.appearance.todayColor = UIColor.clearColor;
_calendar = calendar;
[self.containerView addSubview:self.calendar];
[self.containerView addSubview:self.previousButton];
[self.containerView addSubview:self.nextButton];
[self.previousButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(lineView.mas_bottom).mas_offset(12);
make.centerX.mas_offset(-80);
}];
[self.nextButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.previousButton);
make.centerX.mas_offset(80);
}];
[self.containerView addSubview:self.todayButton];
[self.todayButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(60);
make.height.mas_equalTo(26);
make.centerY.mas_equalTo(self.previousButton);
make.right.mas_offset(10);
}];
[self layoutIfNeeded];
}
#pragma mark - Action
-(void)showInView:(UIView *)view withDate:(NSDate *)date complete:(void (^)(NSDate * _Nullable))completeBlock{
self.completeBlock = completeBlock;
if (view == nil) {
UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.window = window;
[window makeKeyAndVisible];
view = window;
}
[view addSubview:self];
[view bringSubviewToFront:self];
self.frame = view.bounds;
/// 如果传入默认日期 则定位当前页到默认日期
if(date){
[self.calendar setCurrentPage:date animated:NO];
[self.calendar selectDate:date];
}
/// 获取总高度
self.totalHeight = self.containerView.height;
//先把视图隐藏起来
self.containerView.transform = CGAffineTransformMakeTranslation(0,self.totalHeight);
//动画让内容出现
[UIView animateWithDuration:0.25f animations:^{
self.containerView.transform = CGAffineTransformIdentity;
self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.4];
self.alpha = 1;
}];
}
-(void)dismiss{
[UIView animateWithDuration:0.25f animations:^{
self.alpha = 0;
self.containerView.transform = CGAffineTransformMakeTranslation(0, self.totalHeight);
} completion:^(BOOL finished) {
[self removeFromSuperview];
self.window = nil;
}];
}
- (void)previousClicked:(id)sender{
NSDate *currentMonth = self.calendar.currentPage;
NSDate *previousMonth = [self.gregorian dateByAddingUnit:NSCalendarUnitMonth value:-1 toDate:currentMonth options:0];
[self.calendar setCurrentPage:previousMonth animated:YES];
}
- (void)nextClicked:(id)sender{
NSDate *currentMonth = self.calendar.currentPage;
NSDate *nextMonth = [self.gregorian dateByAddingUnit:NSCalendarUnitMonth value:1 toDate:currentMonth options:0];
[self.calendar setCurrentPage:nextMonth animated:YES];
}
- (void)todayItemClicked:(id)sender{
/// 选择today按钮 则默认选择当天
NSDate *today = [NSDate date];
// [self.calendar setCurrentPage:[NSDate date] animated:YES];
[self.calendar selectDate:today];
}
-(void)clearAction{
[self clearSelectDate];
if(self.completeBlock){
self.completeBlock(self.calendar.selectedDate);
}
[self dismiss];
}
-(void)sureAction{
if(self.completeBlock){
self.completeBlock(self.calendar.selectedDate);
}
[self dismiss];
}
-(void)clearSelectDate{
for (NSDate *date in self.calendar.selectedDates) {
[self.calendar deselectDate:date];
}
}
- (NSString *)calendar:(FSCalendar *)calendar titleForDate:(NSDate *)date{
if (date.isToday) {
return @(date.day).stringValue;
}
return nil;
}
-(NSString *)calendar:(FSCalendar *)calendar subtitleForDate:(NSDate *)date{
if (date.isToday) {
return @"今日";
}
return nil;
}
#pragma mark - Lazy
-(UIView *)containerView{
if (!_containerView) {
_containerView = [UIView new];
_containerView.backgroundColor = [UIColor whiteColor];
}
return _containerView;
}
-(UIButton *)nextButton{
if(!_nextButton){
_nextButton = [UIButton new];
[_nextButton setImage:[UIImage imageNamed:@"ic_next"] forState:UIControlStateNormal];
[_nextButton addTarget:self action:@selector(nextClicked:) forControlEvents:UIControlEventTouchUpInside];
}
return _nextButton;
}
-(UIButton *)previousButton{
if(!_previousButton){
_previousButton = [UIButton new];
[_previousButton setImage:[UIImage imageNamed:@"ic_previous"] forState:UIControlStateNormal];
[_previousButton addTarget:self action:@selector(previousClicked:) forControlEvents:UIControlEventTouchUpInside];
}
return _previousButton;
}
-(UIButton *)todayButton{
if(!_todayButton){
_todayButton = [UIButton new];
[_todayButton setTitle:@"今日" forState:UIControlStateNormal];
[_todayButton setTitleColor:OLColorFromRGB(color_text) forState:UIControlStateNormal];
_todayButton.titleLabel.font = [UIFont systemFontOfSize:12];
_todayButton.backgroundColor = OLColorFromRGB(0xF0F0F0);
_todayButton.layer.cornerRadius = 13;
_todayButton.layer.masksToBounds = YES;
[_todayButton addTarget:self action:@selector(todayItemClicked:) forControlEvents:UIControlEventTouchUpInside];
}
return _todayButton;
}
-(UIButton *)clearButton{
if(!_clearButton){
_clearButton = [UIButton new];
[_clearButton setTitle:@"取消选择" forState:UIControlStateNormal];
[_clearButton setTitleColor:OLColorFromRGB(color_mainTheme) forState:UIControlStateNormal];
_clearButton.titleLabel.font = [UIFont systemFontOfSize:14];
[_clearButton addTarget:self action:@selector(clearAction) forControlEvents:UIControlEventTouchUpInside];
}
return _clearButton;
}
-(UIButton *)ensureButton{
if(!_ensureButton){
_ensureButton = [UIButton new];
[_ensureButton setTitle:@"确定" forState:UIControlStateNormal];
[_ensureButton setTitleColor:OLColorFromRGB(color_mainTheme) forState:UIControlStateNormal];
_ensureButton.titleLabel.font = [UIFont systemFontOfSize:14];
[_ensureButton addTarget:self action:@selector(sureAction) forControlEvents:UIControlEventTouchUpInside];
}
return _ensureButton;
}
#pragma mark - LifeStyle
-(void)dealloc{
NSLog(@"%s",__func__);
}
@end