iOS 封装一个简单的日历控件

先看下效果图:


日历.gif

看了一些第三方,改动起来不容易,找了一个demo,跟着思路走了一遍,所以还是动手自己封装一遍。封装尽量保持代码简洁。

首先是布局。

分为三个模块:


布局.png

分为LXCalendarHearder,LXCalendarWeekView以及collectionView封装的日历控件。

headerweekView就不用多说了,最重要的是collectionView的模型建立。有的是把一个月的数据放在一个模型里,想了想还是把每一天的数据单独建立一个模型。

模型的建立

首先是根据当前的日期拿到当月的数据模型,以及上个月下个月的数据模型,因为可能有需要设置上个月和下个月的显示以及灰度处理。

month模型:

@property (nonatomic, strong) NSDate *monthDate; //!< 传入的 NSDate 对象,该 NSDate 对象代表当前月的某一日,根据它来获得其他数据
@property (nonatomic, assign) NSInteger totalDays; //!< 当前月的天数
@property (nonatomic, assign) NSInteger firstWeekday; //!< 标示第一天是星期几(0代表周日,1代表周一,以此类推)
@property (nonatomic, assign) NSInteger year; //!< 所属年份
@property (nonatomic, assign) NSInteger month; //!< 当前月份

- (instancetype)initWithDate:(NSDate *)date;

关于获取上个月下个月的日期,demo里有。

DayModel模型:

@interface LXCalendarDayModel : NSObject
@property (nonatomic, assign) NSInteger totalDays; //!< 当前月的天数
@property (nonatomic, assign) NSInteger firstWeekday; //!< 标示第一天是星期几(0代表周日,1代表周一,以此类推)
@property (nonatomic, assign) NSInteger year; //!< 所属年份
@property (nonatomic, assign) NSInteger month; //!< 当前月份
@property (nonatomic, assign) NSInteger day;   //每天所在的位置

@property(nonatomic,assign)BOOL isLastMonth;//属于上个月的
@property(nonatomic,assign)BOOL isNextMonth;//属于下个月的
@property(nonatomic,assign)BOOL isCurrentMonth;//属于当月

@property(nonatomic,assign)BOOL isToday;//今天

@property(nonatomic,assign)BOOL isSelected;//是否被选中

根据month模型建立DayModel模型:

 LXCalendarDayModel *model =[[LXCalendarDayModel alloc]init];
        
        //配置外面属性
        [self configDayModel:model];
        
        model.firstWeekday = firstWeekday;
        model.totalDays = totalDays;
        
        model.month = monthModel.month;
        
        model.year = monthModel.year;
        
        
        //上个月的日期
        if (i < firstWeekday) {
            model.day = lastMonthModel.totalDays - (firstWeekday - i) + 1;
            model.isLastMonth = YES;
        }
        
        //当月的日期
        if (i >= firstWeekday && i < (firstWeekday + totalDays)) {
            
            model.day = i -firstWeekday +1;
            model.isCurrentMonth = YES;
            
            //标识是今天
            if ((monthModel.month == [[NSDate date] dateMonth]) && (monthModel.year == [[NSDate date] dateYear])) {
                if (i == [[NSDate date] dateDay] + firstWeekday - 1) {
                    
                    model.isToday = YES;
                    
                }                 
            }
            
        }
         //下月的日期
        if (i >= (firstWeekday + monthModel.totalDays)) {
            
            model.day = i -firstWeekday - nextMonthModel.totalDays +1;
            model.isNextMonth = YES;
            
        }

上面的处理主要是把上个月和下个月需要显示的日期和当月的日期全部塞进DayModel模型里。这样我们对cell的处理只需要根据DayModel即可。

细节说明

demo 中只是使用了一个collectionView然后添加左右清扫手势,给collectionViewlayer做一个假假的动画实现翻页效果。

关于日历demo中设置了6 行,具体为什么参考日历的12月份。

最后

demo中设置了一些常见的属性,可以自行配置,需要注意的是在属性设置之后才可以处理数据。

使用方法:

self.calenderView =[[LXCalendarView alloc]initWithFrame:CGRectMake(20, 80, Device_Width - 40, 0)];
    
    self.calenderView.currentMonthTitleColor =[UIColor hexStringToColor:@"2c2c2c"];
    self.calenderView.lastMonthTitleColor =[UIColor hexStringToColor:@"8a8a8a"];
    self.calenderView.nextMonthTitleColor =[UIColor hexStringToColor:@"8a8a8a"];
    
    self.calenderView.isHaveAnimation = NO;
    
    self.calenderView.isCanScroll = YES;
    self.calenderView.isShowLastAndNextBtn = NO;
    
    self.calenderView.isShowLastAndNextDate = YES;

    self.calenderView.todayTitleColor =[UIColor redColor];
    
    self.calenderView.selectBackColor =[UIColor greenColor];
    
    [self.calenderView dealData];
    
    self.calenderView.backgroundColor =[UIColor whiteColor];
    [self.view addSubview:self.calenderView];
    
    self.calenderView.selectBlock = ^(NSInteger year, NSInteger month, NSInteger day) {
        NSLog(@"%ld年 - %ld月 - %ld日",year,month,day);
    };

补充

代码中选中的日期在滑动会清除选中日期,修复代码如下:

 [self.monthdataA enumerateObjectsUsingBlock:^(LXCalendarDayModel * obj, NSUInteger idx, BOOL * _Nonnull stop) {
        
        if ((obj.year == self.selectModel.year) && (obj.month == self.selectModel.month) && (obj.day == self.selectModel.day)) {
            obj.isSelected = YES;
        }
    }];

如果是自定义日历,动画效果会超出显示区域,此时设置
self.calenderView.layer.masksToBounds = YES;裁剪动画区域即可。

最新代码请移步demo 地址。
demo地址:LXCalendar

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 127,691评论 18 546
  • 自然美景一般是天然形成,而没有人工雕琢的景观。看到这个题目,我趁着倒时差的时光,一边收拾东西一边思考这个问题,我究...
    强哥_d8c3阅读 63评论 0 0
  • “年轻所剩无几 但又并没有老得十分彻底 不尴不尬不上不下 真是没什么好讲 情绪也少得可怜 也不愿再花精力去...
    冷漠无情的犬哥阅读 55评论 0 1
  • 每日践行正面管教 大家好,我是正面管教家长讲师Ivy Ding,我陪大家一起践行正面管教。我第27天打卡,相信大家...
    小妖丁阅读 57评论 0 0
  • 我俩都在不同程度的逃避生活,他二十四小时不间断的听小说,玩游戏,我画画,写作,练习英语,我们都不提自己的工作,也不...
    药一味阅读 29评论 0 0