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


posted @ 2023-08-18 11:23  qqcc1388  阅读(1286)  评论(0编辑  收藏  举报