iOS开发一款小巧简洁的日历控件

jopen 9年前

iOS开发一款小巧简洁的日历控件

一、引言

        日 历是iOS开发中有时会用到的一个UI控件,网上开源的代码也很多,我浏览过一些,大致有两种模式,一种是日历的逻辑由开发者自己实现,通过计算闰年与平 年来确定月份天数,另外一种模式是通过NSDate这个时间类,来获取日历的信息。我个人认为后一种更加安全,代码性能也会更加优质,下面就是我用这种模 式实现的一个日历控件。

二、设计思路

1、先来看下效果吧

iOS开发一款小巧简洁的日历控件        iOS开发一款小巧简洁的日历控件           iOS开发一款小巧简洁的日历控件

2、我们需要实现的功能

(1)每行7天,对应星期,列数为将当前月显示完全

(2)今日标红

(3)点击的日期背景填充

(4)提供特殊标记,用于标记计划日,节日等

(5)左右无限翻页,直到世界起源和末日

 3、设计步骤

(1)设计一个日历模型

#import "YHBaseModel.h"    @interface YHBaseDateModel : YHBaseModel  @property(nonatomic,strong)NSString * year;  @property(nonatomic,strong)NSString * month;  @property(nonatomic,strong)NSString * day;  @end

(2)向系统的NSDate类中添加一些扩展方法,便于我们使用

//头文件部分  @interface NSDate (YHBaseCalendar)  /**   *获取当前月的天数   */  - (NSUInteger)YHBaseNumberOfDaysInCurrentMonth;  /**   *获取本月第一天   */  - (NSDate *)YHBaseFirstDayOfCurrentMonth;  //下面这些方法用于获取各种整形的数据  /**   *确定某天是周几   */  -(int)YHBaseWeekly;  /**   *年月日 时分秒   */  -(int)getYear;  -(int)getMonth;  -(int)getDay;  -(int)getHour;  -(int)getMinute;  -(int)getSecond;  @end    //实现部分  @implementation NSDate (YHBaseCalendar)  -(NSUInteger)YHBaseNumberOfDaysInCurrentMonth{       return [[NSCalendar currentCalendar] rangeOfUnit:NSDayCalendarUnit inUnit:NSMonthCalendarUnit forDate:self].length;    }  - (NSDate *)YHBaseFirstDayOfCurrentMonth  {      NSDate *startDate = nil;      BOOL ok = [[NSCalendar currentCalendar] rangeOfUnit:NSMonthCalendarUnit startDate:&startDate interval:NULL forDate:self];      NSAssert1(ok, @"Failed to calculate the first day of the month based on %@", self);      return startDate;  }  -(int)YHBaseWeekly{       return (int)[[NSCalendar currentCalendar] ordinalityOfUnit:NSDayCalendarUnit inUnit:NSWeekCalendarUnit forDate:self];  }        -(int)getYear{      NSCalendar *calendar = [NSCalendar currentCalendar];      NSUInteger unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;      NSDateComponents *dateComponent = [calendar components:unitFlags fromDate:self];      return (int)dateComponent.year;  }  -(int)getMonth{      NSCalendar *calendar = [NSCalendar currentCalendar];      NSUInteger unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;      NSDateComponents *dateComponent = [calendar components:unitFlags fromDate:self];      return (int)dateComponent.month;  }  -(int)getDay{      NSCalendar *calendar = [NSCalendar currentCalendar];      NSUInteger unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;      NSDateComponents *dateComponent = [calendar components:unitFlags fromDate:self];      return (int)dateComponent.day;  }  -(int)getHour{      NSCalendar *calendar = [NSCalendar currentCalendar];      NSUInteger unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;      NSDateComponents *dateComponent = [calendar components:unitFlags fromDate:self];      return (int)dateComponent.hour;  }  -(int)getMinute{      NSCalendar *calendar = [NSCalendar currentCalendar];      NSUInteger unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;      NSDateComponents *dateComponent = [calendar components:unitFlags fromDate:self];      return (int)dateComponent.minute;  }  -(int)getSecond{      NSCalendar *calendar = [NSCalendar currentCalendar];      NSUInteger unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;      NSDateComponents *dateComponent = [calendar components:unitFlags fromDate:self];      return (int)dateComponent.second;  }  @end

(3)设计我们的UI控件

//头文件部分  @interface YHBaseCalendarView : YHBaseView  @property(nonatomic,strong)NSDate * currentDate;  //标记数组 用于标记特殊日期 这个数组中存放的必须是YHBaseDateModel 对象  @property(nonatomic,strong)NSArray * markArray;  @property(nonatomic,weak)id<YHBaseCalendarViewDelegate> delegate;  @end  //实现部分  @interface YHBaseCalendarView()<UIScrollViewDelegate>  {      //星期      UIView * _headView;      //日历的展示      UIView * _bodyViewL;      UIView * _bodyViewM;      UIView * _bodyViewR;      //滑动功能的支持      UIScrollView * _scrollView;      NSDate * _today;            YHBaseDateModel * _selectModel;  }  @end  @implementation YHBaseCalendarView  -(void)reloadView{      _currentDate = [NSDate date];      _today = [NSDate date];      _selectModel = [[YHBaseDateModel alloc]init];      _selectModel.year = [NSString stringWithFormat:@"%d",[_today getYear]];      _selectModel.month =[NSString stringWithFormat:@"%d",[_today getMonth]];      _selectModel.day = [NSString stringWithFormat:@"%d",[_today getDay]];      _scrollView = [[UIScrollView alloc]initWithFrame:CGRectMake(0, 30, self.frame.size.width, self.frame.size.height)];      _scrollView.contentSize = CGSizeMake(3*self.frame.size.width, 0);      _scrollView.contentOffset = CGPointMake(self.frame.size.width, 0);      _scrollView.pagingEnabled=YES;      _scrollView.delegate=self;      [self addSubview:_scrollView];      _bodyViewL = [[UIView alloc]initWithFrame:CGRectMake(0, 0, _scrollView.frame.size.width, _scrollView.frame.size.height)];      [_scrollView addSubview:_bodyViewL];      _bodyViewM = [[UIView alloc]initWithFrame:CGRectMake(_scrollView.frame.size.width,0,  _scrollView.frame.size.width, _scrollView.frame.size.height)];      [_scrollView addSubview:_bodyViewM];      _bodyViewR = [[UIView alloc]initWithFrame:CGRectMake(_scrollView.frame.size.width*2, 0, _scrollView.frame.size.width, _scrollView.frame.size.height)];      [_scrollView addSubview:_bodyViewR];      //展示星期      _headView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, self.frame.size.width, 30)];      _headView.backgroundColor = [UIColor redColor];      NSArray * weekArray = @[@"SUN",@"MON",@"TUES",@"WED",@"THUR",@"FRI",@"SAT"];      for (int i=0; i<7; i++) {          UILabel * label = [[UILabel alloc]initWithFrame:CGRectMake(self.frame.size.width/7*i, 0, self.frame.size.width/7, 30)];          if (i!=0&&i!=6) {              label.backgroundColor = [UIColor redColor];          }else{              label.backgroundColor = [UIColor purpleColor];          }          label.text=weekArray[i];          label.textAlignment = NSTextAlignmentCenter;          label.layer.borderWidth=1;          label.layer.borderColor = [[UIColor grayColor]CGColor];          label.font = [UIFont boldSystemFontOfSize:16];          label.layer.borderColor=[[UIColor grayColor] CGColor];          label.textColor = [UIColor whiteColor];          label.layer.borderWidth = 1;          [_headView addSubview:label];      }      [self addSubview:_headView];            [self creatViewWithData:_currentDate onView:_bodyViewM];      [self creatViewWithData:[YHBaseDateTools getPreviousframDate:_currentDate] onView:_bodyViewL];      [self creatViewWithData:[YHBaseDateTools getNextMonthframDate:_currentDate] onView:_bodyViewR];  }  //核心的构造方法  -(void)creatViewWithData:(id)data onView:(UIView *)bodyView{      NSDate * currentDate = (NSDate *)data;      //获取当前月有多少天      int monthNum = (int)[currentDate YHBaseNumberOfDaysInCurrentMonth];      //获取第一天的日期      NSDate * firstDate = [currentDate YHBaseFirstDayOfCurrentMonth];      //确定这一天是周几      int weekday = [firstDate YHBaseWeekly];      //确定创建多少行      int weekRow=0;      int tmp=monthNum;      if (weekday!=7) {          weekRow++;          tmp=monthNum-(7-weekday);      }      weekRow += tmp/7;      weekRow += (tmp%7)?1:0;      //开始创建按钮      /**       *这里的逻辑是有问题的,应该设计成cell的复用机制,而不应该重复耗性能的创建 有时间在优化       */  #warning 可以优化哦       NSArray * array = [bodyView subviews];      for (UIView * v in array) {          [v removeFromSuperview];      }      int nextDate = 1;      //行      for (int i=0; i<weekRow; i++) {          //列          for (int j=0; j<7; j++) {              //先进行上个月余天的创建              UIButton * btn;              if (weekday!=7&&(i*7+j)<weekday) {                  //获取上个月有多少天                  NSDate * preDate = [YHBaseDateTools getPreviousframDate:currentDate];                  int preDays = (int)[preDate YHBaseNumberOfDaysInCurrentMonth];                  btn =[[UIButton alloc]initWithFrame:CGRectMake(self.frame.size.width/7*j, self.frame.size.width/7*i, self.frame.size.width/7, self.frame.size.width/7)];                  [btn setTitle:[NSString stringWithFormat:@"%d",preDays-weekday+j+1] forState:UIControlStateNormal];                  [btn setTitleColor:[UIColor grayColor] forState:UIControlStateNormal];                  [bodyView addSubview:btn];              }else if((i*7+j+1-(weekday==7?0:weekday))<=monthNum){                  btn =[[UIButton alloc]initWithFrame:CGRectMake(self.frame.size.width/7*j, self.frame.size.width/7*i, self.frame.size.width/7, self.frame.size.width/7)];                  [btn setTitle:[NSString stringWithFormat:@"%d",(i*7+j+1-(weekday==7?0:weekday))] forState:UIControlStateNormal];                  [btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];                  [bodyView addSubview:btn];              }else{                  btn =[[UIButton alloc]initWithFrame:CGRectMake(self.frame.size.width/7*j, self.frame.size.width/7*i, self.frame.size.width/7, self.frame.size.width/7)];                  [btn setTitle:[NSString stringWithFormat:@"%d",nextDate++] forState:UIControlStateNormal];                  [btn setTitleColor:[UIColor grayColor] forState:UIControlStateNormal];                  [bodyView addSubview:btn];              }              //将今天的日期标出              if ([currentDate getYear]==[_today getYear]&&[currentDate getMonth]==[_today getMonth]&&[btn.titleLabel.text intValue]==[_today getDay]&&!CGColorEqualToColor([btn.titleLabel.textColor CGColor], [[UIColor grayColor] CGColor])) {                  [btn setTitleColor:[UIColor redColor] forState:UIControlStateNormal];              }              //是否进行自定义标记              /**               *if中的颜色比较 是为了让上月与下月的余日不产生bug               */              if (_markArray!=nil) {                  for (int i=0; i<_markArray.count; i++) {                      YHBaseDateModel * model = _markArray[i];                      if ([currentDate getYear]==[model.year intValue]&&[currentDate getMonth]==[model.month intValue]&&[btn.titleLabel.text intValue]==[model.day intValue]&&!CGColorEqualToColor([btn.titleLabel.textColor CGColor], [[UIColor grayColor] CGColor])) {                          btn.layer.borderColor = [[UIColor grayColor]CGColor];                          btn.layer.borderWidth=1;                      }                  }              }              //是否进行选中标记              if ([_selectModel.year intValue]==[currentDate getYear]&&[_selectModel.month intValue]==[currentDate getMonth]&&[_selectModel.day intValue]==[btn.titleLabel.text intValue]&&!CGColorEqualToColor([btn.titleLabel.textColor CGColor], [[UIColor grayColor] CGColor])) {                  btn.backgroundColor = [UIColor cyanColor];              }              if (!CGColorEqualToColor([btn.titleLabel.textColor CGColor], [[UIColor grayColor] CGColor])) {                  //添加点击事件                  [btn addTarget:self action:@selector(clickBtn:) forControlEvents:UIControlEventTouchUpInside];              }                       }      }        }  //这个方法中进行重构  -(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{      if (scrollView.contentOffset.x==0) {//向前翻页了          _currentDate = [YHBaseDateTools getPreviousframDate:_currentDate];          _scrollView.contentOffset=CGPointMake(scrollView.frame.size.width, 0);                    [self creatViewWithData:_currentDate onView:_bodyViewM];          [self creatViewWithData:[YHBaseDateTools getPreviousframDate:_currentDate] onView:_bodyViewL];          [self creatViewWithData:[YHBaseDateTools getNextMonthframDate:_currentDate] onView:_bodyViewR];                }else if (scrollView.contentOffset.x==scrollView.frame.size.width){                }else if (scrollView.contentOffset.x==scrollView.frame.size.width*2){          _currentDate = [YHBaseDateTools getNextMonthframDate:_currentDate];          _scrollView.contentOffset=CGPointMake(scrollView.frame.size.width, 0);                    [self creatViewWithData:_currentDate onView:_bodyViewM];          [self creatViewWithData:[YHBaseDateTools getPreviousframDate:_currentDate] onView:_bodyViewL];          [self creatViewWithData:[YHBaseDateTools getNextMonthframDate:_currentDate] onView:_bodyViewR];      }      scrollView.userInteractionEnabled=YES;      if ([self.delegate respondsToSelector:@selector(YHBaseCalendarViewScrollEndToDate:)]) {          YHBaseDateModel * model = [[YHBaseDateModel alloc]init];          model.year = [NSString stringWithFormat:@"%d",[_currentDate getYear]];          model.month = [NSString stringWithFormat:@"%d",[_currentDate getMonth]];          model.day = [NSString stringWithFormat:@"%d",[_currentDate getDay]];          [self.delegate YHBaseCalendarViewScrollEndToDate:model];      }  }      -(void)scrollViewDidScroll:(UIScrollView *)scrollView{      scrollView.userInteractionEnabled=NO;  }    //点击事件  -(void)clickBtn:(UIButton *)btn{      _selectModel.year = [NSString stringWithFormat:@"%d",[_currentDate getYear]];      _selectModel.month = [NSString stringWithFormat:@"%d",[_currentDate getMonth]];      _selectModel.day = btn.titleLabel.text;      [self creatViewWithData:_currentDate onView:_bodyViewM];      [self creatViewWithData:[YHBaseDateTools getPreviousframDate:_currentDate] onView:_bodyViewL];      [self creatViewWithData:[YHBaseDateTools getNextMonthframDate:_currentDate] onView:_bodyViewR];      if ([self.delegate respondsToSelector:@selector(YHBaseCalendarViewSelectAtDateModel:)]) {          [self.delegate YHBaseCalendarViewSelectAtDateModel:_selectModel];      }        }    @end

(4)为用户交互设计的协议

@protocol YHBaseCalendarViewDelegate<NSObject>  -(void)YHBaseCalendarViewSelectAtDateModel:(YHBaseDateModel *)dateModel;  -(void)YHBaseCalendarViewScrollEndToDate:(YHBaseDateModel *)dateModel;  @end

三、插个小广告

        控件的源码在https://github.com/ZYHshao/YHBaseFoundationTest.git中,这是我封装的一套基于Cocoa与Foundation的更易用的开发框架,其中也对AFN,CRLabel,SDImage,MJRefresh进行了集成,有易用的下载框架,缓存框架,错误处理框架,皮肤管理框架等,也有支持加载HTML并且异步缓存图片的view,边下边播并做缓存的AVAudioPlayer,以及各种自定义性能很强的view控件,如用block创建的按钮,提示框以及对json和模型做相关映射的处理类,如果这些东西有帮到你,我很开心,如果你发现一些问题或者优化建议,请一定告知我,我将十分感激,QQ316045346

来自:http://my.oschina.net/u/2340880/blog/502361