作为一名健身爱好者,使用Keep也有三千分钟了,最近对他们的根据日期显示View界面很感兴趣,忍不住动手仿写了一个。示例图:
首先来分析一下,它的日期包含三个部分,上个月的最后几天,当前月数的天数,下个月的头几天。如图:
1.上个月的最后几天
2.下个月的头几天
3.中间的当前月的天数,这个不需要展示了,gif图里面的中间显示就是
我将界面整体分为上下两个部分,我们一个一个来分析,我会用尽可能详细的方法来描诉,而且我的Demo里有非常详细的注释。
上面的滚动条负责显示日期,而日期的显示通过button来实现。滚动条用scrollView来生成,其显示方式为:一次显示七个button,分别代表周末-周六,每个button都代表一个日期为几月几号。日期的显示为:周几-日期。如:星期五,七月一号则显示为五-1。而如果button的日期为今天则显示为:今天。
此时出现两种情况,第一:若前月的第一天不是周末,就需要通过获取上个月的天数,然后计算所需要的最后几天为几月几号星期几。第二:若当前月的最后一天不是周六,则需要显示下个月的头几天为几月几号星期几。
不过对于这两个问题的解决,我们只需要获取上个月的天数即可,详情后面解释。
同时,对于滚动条上的button而言,每一个button都代表一天,我们要通过button的点击切换下面的页面。所以,为了让按钮更好地保存日期与周几的信息,且不会与用来识别button的tag属性混淆,我用了一个自定义的button,只添加了两个属性:
//代表按钮是几月几号
@property(nonatomic,assign)NSInteger weekday;
//代表按钮是几号
@property(nonatomic,assign)NSInteger day;
而且为了以后的开发适应范围更广,此处显示日期的滚动条宽度不为屏幕的宽度。
而对于下面的View页面显示,则每一个View代表一天,View页面包含两种:代表今天的View,不代表今天的View。不代表今天的View上有一个按钮:回到今天,点击后可跳转到代表今天的View页面。而代表今天的View则显示为此处是今天的页面。
因此根据分析,我们首先需要获取当前月有多少天,在计算之前我们先熟悉一下NSCalendar(日历),我的Demo中,日历的默认算法都是公历
//获取算法为公历的日历
NSCalendar * calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
而后通过公历的日历,获取当前月有多少天,核心代码如下:
//给定当前日期
NSDate * currentDate = [NSDate date];
//计算当前月有多少天
NSRange range = [calendar rangeOfUnit:NSCalendarUnitDay
inUnit: NSCalendarUnitMonth
forDate:currentDate];
//range的长度即当前月的天数
return range.length;
然后,我们仍旧通过系统提供NSCalendar的算法获取当前页中所有天数都是周几,但在获取之前我们需要获取指定格式的日期:年 - 月 - 此月的第几天,而在获取到此格式之前,我们还需要先确定一下当前的时间为X年X月,详情可参考以下代码:
//指定日期的显示格式为年- 月
NSDateFormatter * formatter = [[NSDateFormatter alloc] init];
NSDate * currentDate = [NSDate date];
[formatter setDateFormat:@"yyyy-MM"];
//获取 年 - 月
NSString * str = [formatter stringFromDate:currentDate];
// NSLog(@"%@",str);
随后可通过循环遍历当前月的天数来拼接格式:年 - 月 - 第几天,核心代码:
//声明格式为:年 - 月 - 第几天
[formatter setDateFormat:@"yyyy-MM-dd"];
//用来保存周几的数组
NSMutableArray * allDaysArray = [[NSMutableArray alloc] init];
//当前月有多少天就循环多少次
for (NSInteger i = 1; i <= dayCount; i++) {
//获取时间格式如: 年 - 月 - 1 、 年 - 月 - 2
NSString * sr = [NSString stringWithFormat:@"%@-%ld",str,i];
NSDate *suDate = [formatter dateFromString:sr];
//通过时间自定义方法获取第几日为星期几
[allDaysArray addObject:[self getweekDayWithDate:suDate]];
}
而自定义方法getweekDayWithDate里面所做的事情就很简单了,通过日历为公历的NSCalendar方法来计算传进来的日期是星期几:
- (id) getweekDayWithDate:(NSDate *) date
{
//指定日历的算法为公历
NSCalendar * calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
//获取指定日期为周几
NSDateComponents *comps = [calendar components:NSCalendarUnitWeekday fromDate:date];
// 若不减一 则 1 是周日,2是周一 3是周二 以此类推
// 而减一后 则 0为周日,1为周一,2为周二 以此类推
return @([comps weekday]-1);
}
至此,我们获取到的数据有:当前月有多少天,每天都是周几。
而在设置滚动条之前我们还应该先获取当前月的第一天是周几,我通过将当前月每天都是周几返回的数据用数组保存,就很轻松的可以获得第一天为周几,代码如下:
//获取当前月都是周几
NSArray *dayArr = [self getAllDaysWithCalender];
//获取一号的时候是周几
NSInteger day = [dayArr[0] integerValue];
随后开始设置显示日期的button的滚动条了,在设置时,由于当前月第一天不是周日,就需要获取上个月的最后几天,因此我们需要先获取上个月总共有多少天,依旧是取算法为公历的日历,随后通过NSDateComponets计算上个月的月数是几月,核心代码如下:
//获取当前日期
NSDate * mydate = [NSDate date];
//设置关于日期的算法为上个月
NSDateComponents *adcomps = [[NSDateComponents alloc]init];
[adcomps setMonth:-1];
//设置日期格式为MM 月
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
[dateFormatter setDateFormat:@"MM"];
//通过指定算法获取上个月,并转换格式
NSDate *yesterMonDate = [calendar dateByAddingComponents:adcomps toDate:mydate options:0 ];
NSString *beforeDate = [dateFormatter stringFromDate:yesterMonDate];
而后通过获取到NSCalendar提供的算法,获取这个月中总共有多少天:
//获取上个月有多少天
NSRange range = [calendar rangeOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitMonth forDate:yesterMonDate];
到此,我们得到了上个月的天数,可以计算需要显示的button信息都为哪些。
我是通过for循环来计算的,for循环条件为int i=0; i<day;i++,day为当前月的第一号为周几,因为从周日开始排,所以周几就代表前面有几天,获取上个月的天数减去i值即为所需显示的前几天的日期为几号,而且是从周日开始排,我一开始还以为要再去计算得到周日那天几号,算到这步才醒悟不用算~
for循环的内部核心代码:
//按钮的frame会在添加时重新设置,所以此处设为0即可
//我为了能看到按钮的正确生成而保存着
//创建所需
ButtonOfWeek *btn = [[ButtonOfWeek alloc]initWithFrame:CGRectMake(i*50, i*30, 50, 30)];
//设置背景色为随机色
btn.backgroundColor = [UIColor colorWithRed:(arc4random_uniform(256)/256.0) green:(arc4random_uniform(256)/256.0) blue:(arc4random_uniform(256)/256.0) alpha:1];
//获取上个月的天数
NSInteger num = [self getNumberOfDaysInYesterMonth];
//获取显示的前几天的日期
NSInteger dayNum = num - i;
// NSLog(@"%ld",dayNum);
//获取上个月的月数跟计算出的几号拼接成weekday值
//用来识别按钮代表的是几月几号
NSString *dayNumOfStr = [NSString stringWithFormat:@"%ld%02ld",self.offNum,dayNum];
// NSLog(@"%@",dayNumOfStr);
// NSLog(@"%@",dayNumOfStr);
//设置按钮代表第几号,如7月23号的23号
btn.day = dayNum;
//设置按钮的weekday值,也就是日期号
//如:七月一号为701号,七月十三号713号
btn.weekday = [dayNumOfStr integerValue];
//将按钮添加到数组
[buttonArr addObject:btn];
在这里解释一下,buttonArr用来保存滚动条上所有的button,我们先保存所需要生成的所有button,再通过for循环遍历添加并重新布局.
在上个月的button显示之后即为当前月的Button信息,获取的方法无外乎刚才说的那几种,直接通过for循环既可以获取所需的全部button,核心代码如下,num值为当前月的天数:
for (int i = 0; i < num; i++) {
ButtonOfWeek *btn = [[ButtonOfWeek alloc]initWithFrame:CGRectMake(i%7*50, i/7*30,50, 30)];
//设置背景颜色随机
btn.backgroundColor = [UIColor colorWithRed:(arc4random_uniform(256)/256.0) green:(arc4random_uniform(256)/256.0) blue:(arc4random_uniform(256)/256.0) alpha:1];
//设置日期
NSDate *date = [NSDate date];
//获取当前的月数
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
[dateFormatter setDateFormat:@"MM"];
//将获取到的月数转成字符串
NSString *str = [dateFormatter stringFromDate:date];
//通过月数几号拼接成字符串weekday
//设置weekday值为701,712这种格式,方便后面判断
NSString *weekStr = [NSString stringWithFormat:@"%@%02d",str,i+1];
//注意:转换成integerValue后,0716会变成716
NSInteger weekday = [weekStr integerValue];
//设置weekday值
btn.weekday = weekday;
//设置按钮显示的是几号
btn.day = i+1;
// NSLog(@"%ld",tag);
//将按钮添加到数组
[buttonArr addObject:btn];
}
获取了当前月所需显示的button后,就要考虑若当前月的月底不为周六,则需要补充几个代表下月的button信息。
解决思路很简单:当前按钮如果显示到周一,就代表要填充6个按钮为下个月的按钮,所以通过求余计算要填充几个按钮,NSInteger afterNum = 7-buttonArr.count%7;
而如果当前月的月底正好为周六,就代表afterNum的值为7,此时不添加Button,核心代码如下:
NSInteger afterNum = 7-buttonArr.count%7;
//如果要显示的是七天,就一天也不显示
if (afterNum != 7) {
for (int i = 0; i<afterNum;i++){
ButtonOfWeek *btn = [[ButtonOfWeek alloc]init];
//设置要显示的日期,如1号,2号
btn.day = self.buttonArr.count%7+i;
//背景颜色随机
btn.backgroundColor = [UIColor colorWithRed:(arc4random_uniform(256)/256.0) green:(arc4random_uniform(256)/256.0) blue:(arc4random_uniform(256)/256.0) alpha:1];
//到此保存了所有的按钮
[buttonArr addObject:btn];
}
}
获取了滚动条上所有的Button就可以进行添加了,在书写添加前,先确定一下按钮的的信息设置:tag用来识别按钮,weekday保存的是Button代表的几月几号,day表示button代表的是周几。而weekday与day的值我们都已经获取并保存,tag值在循环时添加,所以代码如下:
//设置滚动的ScrollView
UIScrollView *scrollV = [[UIScrollView alloc]initWithFrame:CGRectMake(10, 100, 350, 33)];
//循环遍历保存的按钮,依次取出并设置
//按钮的设置
for (int i = 0; i < buttonArr.count; i++) {
//获取按钮重新设置Frame
ButtonOfWeek *btn = buttonArr[i];
btn.frame = CGRectMake(i*50, 0, 50, 30);
//按钮上的星期几就是i的值,0为周日,1为周一,依次排列
//将周几从0、1、2格式转成日、一、二格式
NSString *weekday = self.weekArr[i%7];
//设置按钮的显示文字
//如果按钮代表的日期是今天,就显示几年
//否则就显示:“日-日期”,如7月6日星期三为:“三-6”,7月3日星期日为:“日-3”
//先获取今天是几月几号
NSInteger today = [self getNumberOfToday];
//根据按钮保存的几月几号与今天日期进行判断
if (today == btn.weekday) {
[btn setTitle:[NSString stringWithFormat:@"今日"] forState:UIControlStateNormal];
//保存代表今日的按钮
self.todayButton = btn;
}else{
[btn setTitle:[NSString stringWithFormat:@"%@-%ld",weekday,btn.day] forState:UIControlStateNormal];
}
// NSLog(@"%ld",btn.weekday);
//将按钮添加到ScrollView并延长ScrollV的滚动范围
[scrollV addSubview:btn];
//用tag保存当前是第几个按钮
btn.tag = i;
//添加点击事件
[btn addTarget:self action:@selector(clickButtoOfDay:) forControlEvents:UIControlEventTouchUpInside];
//设置偏移量
scrollV.contentSize = CGSizeMake(scrollV.contentSize.width+btn.frame.size.width, 0);
}
而后开启scrollView的滚动分页效果(pagingEnabled),并用全局属性保存含有显示button的ScrollView,而后添加ScrollView到当前View
在按钮的下方有一个红色标志,红色的标志条为一个View,随着按钮的点击而改变自身frame,而初始化时frame就位于按钮的下方,高度为2。
上半部分的按钮显示设置完毕后,就可以动手设置下半部分的View显示界面,关于分页效果、frame、滚动值这些我就不多说了,详情看下Demo即可,很简单的。
关于显示View的分析为:View的宽度是屏幕的宽度,有多少个button就设置多少个View,背景颜色随机,代表是不同的View。显示结果有两种:代表今日的VIew显示一个Label信息,不是今日的View显示一个Button,若需要可修改button的透明度alpha(我观察后觉得keep里就修改了)。
View显示内容是否是今日的判断,通过已保存的代表今日的按钮(代码中有写)的tag值与循环时的i值来比较判断。
核心代码如下:
//给ScrollView的每一页添加一个显示View
for (int i = 0;i < self.buttonArr.count; i++) {
//设置显示View
UIView *baskV = [[UIView alloc]initWithFrame:CGRectMake(i*W, 0, bottomScrollV.frame.size.width, bottomScrollV.frame.size.height)];
//设置View的颜色为随机颜色
baskV.backgroundColor = [UIColor colorWithRed:(arc4random_uniform(256)/256.0) green:arc4random_uniform(256)/256.0 blue:arc4random_uniform(256)/256.0 alpha:1];
//通过代表今日的按钮的tag值与i比较,得到当前View代表的是今日界面
if (i == self.todayButton.tag) {
UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(100, 100, 150, 100)];
label.text = @"这里是今日界面!";
[baskV addSubview:label];
}else{
//给每个View添加一个按钮为回到今天
UIButton *backToday = [[UIButton alloc]initWithFrame:CGRectMake(100, 100, 100, 44)];
[backToday setTitle:@"回到今日" forState:UIControlStateNormal];
//回到今日的按钮点击事件
[backToday addTarget:self action:@selector(clickBackToday:) forControlEvents:UIControlEventTouchUpInside];
backToday.backgroundColor = [UIColor greenColor];
backToday.alpha = 0.3;
[baskV addSubview:backToday];
}
//将View添加到ScrollV上
[bottomScrollV addSubview:baskV];
}
接下来,是对显示日期的button的点击事件与View中button的点击事件实现。
View中的Button点击事件很简单:
#pragma mark - 点击返回今日的按钮
-(void)clickBackToday:(UIButton *)sender{
//滚动上面的ScrollView
//滚动范围X为一页的宽度 * 滚动的页数
[self.topScrollV setContentOffset:CGPointMake(self.todayButton.tag/7*(7*self.todayButton.frame.size.width), 0) animated:YES];
//滚动下面的ScrollView
[self clickButtoOfDay:self.todayButton];
}
显示日期的Button的点击事件
//因为偏移量无论如何都要计算
//所以我选择ScrollView方式,不过也可以用CollectionView来写
-(void)clickButtoOfDay:(ButtonOfWeek *)sender{
//改变标志View的frame
CGSize lineSize = self.lineView.frame.size;
self.lineView.frame = CGRectMake(sender.tag*sender.frame.size.width, self.lineView.frame.origin.y, lineSize.width, lineSize.height);
//滚动底部的ScrollView
[self.bottomScrollV setContentOffset:CGPointMake(sender.tag*W, 0) animated:YES];
}
Demo地址:https://github.com/DrunkenMouse/copyKeepOfDate
最后说几点:
按钮的状态包括普通、高亮、选中、禁选(enable与userInteractionEnabled,前者会进入disabled状态后者不会)
而我观看keep页面上的按钮状态则分为以下几种:
禁选时修改alpha值改变透明度,并修改字体颜色,或通过enable的disabled状态
休息日时字体颜色为黑色,背景颜色为白色
非休息日时字体颜色为黑色,背景颜色为灰色并切圆,或直接设置一张圆形图片(若切圆,建议开启保存缓存方法)
训练结束时按钮添加一个小图标,小图标可通过选中与否切换图片显示,或按钮上加一个imageView\Button来显示图标,然后hidden来决定显示与否
如果是代表当前日期的按钮,则修改字体颜色为绿色