logo头像

学如逆水行舟

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

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

一、引言

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

二、设计思路

1、先来看下效果吧

2、我们需要实现的功能

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

(2)今日标红

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

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

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

3、设计步骤

(1)设计一个日历模型

1
2
3
4
5
6
7
#import "YHBaseModel.h"

@interface YHBaseDateModel : YHBaseModel
@property(nonatomic,strong)NSString * year;
@property(nonatomic,strong)NSString * month;
@property(nonatomic,strong)NSString * day;
@end

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
//头文件部分
@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控件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
//头文件部分
@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)为用户交互设计的协议

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

三、插个小广告

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

专注技术,热爱生活,交流技术,也做朋友。

——珲少 QQ群:203317592