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

简介:

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

一、引言

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

二、设计思路

1、先来看下效果吧

151734_UMxO_2340880.png        151735_BsEo_2340880.png           151735_ZUgv_2340880.png

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
目录
相关文章
|
3天前
|
API 数据安全/隐私保护 iOS开发
利用uni-app 开发的iOS app 发布到App Store全流程
利用uni-app 开发的iOS app 发布到App Store全流程
114 3
|
3天前
|
存储 iOS开发
iOS 开发,如何进行应用的本地化(Localization)?
iOS 开发,如何进行应用的本地化(Localization)?
128 2
|
3天前
|
存储 数据建模 数据库
IOS开发数据存储:什么是 UserDefaults?有哪些替代方案?
IOS开发数据存储:什么是 UserDefaults?有哪些替代方案?
47 0
|
3天前
|
安全 编译器 Swift
IOS开发基础知识: 对比 Swift 和 Objective-C 的优缺点。
IOS开发基础知识: 对比 Swift 和 Objective-C 的优缺点。
108 2
|
3天前
|
API 开发工具 Android开发
iOS 和 Android 平台的开发有哪些主要区别?
iOS与Android开发区别:iOS用Objective-C/Swift,App Store唯一下载渠道;Android用Java/Kotlin,多商店发布(如Google Play、华为市场)。设计上,iOS简洁一致,Android灵活可定制。开发工具,iOS用Xcode,Android用Android Studio。硬件和系统多样性,iOS统一,Android复杂。权限管理、审核流程及API各有特点,开发者需依据目标平台特性进行选择。
39 3
|
3天前
|
前端开发 Android开发 iOS开发
【Flutter前端技术开发专栏】Flutter在Android与iOS上的性能对比
【4月更文挑战第30天】Flutter 框架实现跨平台移动应用,通过一致的 UI 渲染(Skia 引擎)、热重载功能和响应式框架提高开发效率和用户体验。然而,Android 和 iOS 的系统差异、渲染机制及编译过程影响性能。性能对比显示,iOS 可能因硬件优化提供更流畅体验,而 Android 更具灵活性和广泛硬件支持。开发者可采用代码、资源优化和特定平台优化策略,利用性能分析工具提升应用性能。
【Flutter前端技术开发专栏】Flutter在Android与iOS上的性能对比
|
3天前
|
存储 Swift iOS开发
使用Swift开发一个简单的iOS应用的详细步骤。
使用Swift开发iOS应用的步骤包括:创建Xcode项目,设计界面(Storyboard或代码),定义数据模型,实现业务逻辑,连接界面和逻辑,处理数据存储(如Core Data),添加网络请求(必要时),调试与测试,根据测试结果优化改进,最后提交至App Store或其它平台发布。
39 0
|
3天前
|
安全 Swift iOS开发
【Swift 开发专栏】Swift 与 UIKit:构建 iOS 应用界面
【4月更文挑战第30天】本文探讨了Swift和UIKit在构建iOS应用界面的关键技术和实践方法。Swift的简洁语法、类型安全和高效编程模型,加上与UIKit的紧密集成,使开发者能便捷地创建用户界面。UIKit提供视图、控制器、布局、动画和事件处理等功能,支持灵活的界面设计。实践中,遵循设计原则,合理组织视图层次,运用布局和动画,以及实现响应式设计,能提升界面质量和用户体验。文章通过登录、列表和详情界面的实际案例展示了Swift与UIKit的结合应用。
|
3天前
|
存储 安全 Swift
【Swift 开发专栏】使用 Swift 开发一个简单的 iOS 应用
【4月更文挑战第30天】本文介绍了使用 Swift 开发简单 iOS 待办事项应用的步骤。首先,阐述了 iOS 开发的吸引力及 Swift 语言的优势。接着,详细说明了应用的需求和设计,包括添加、查看和删除待办事项的功能。开发步骤包括创建项目、界面搭建、数据存储、功能实现,并提供了相关代码示例。最后,强调了实际开发中需注意的细节和优化,旨在帮助初学者掌握 Swift 和 iOS 开发基础。
|
3天前
|
iOS开发 开发者 UED
利用SwiftUI构建动态列表:iOS开发的新范式
【4月更文挑战第22天】在本文中,我们将深入探讨如何使用SwiftUI来创建动态列表。SwiftUI是苹果最新推出的用户界面工具集,它允许开发者以声明式的方式描述用户界面,从而简化了代码的复杂性。我们将通过具体的代码实例,展示如何利用SwiftUI的List和ForEach视图来创建动态列表,并讨论其在实际开发中的应用。
21 2