iOS 仿Keep,动态获取当前日期,根据日期选择界面,日期以周为单位显示

     作为一名健身爱好者,使用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来决定显示与否

如果是代表当前日期的按钮,则修改字体颜色为绿色

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 1.badgeVaule气泡提示 2.git终端命令方法> pwd查看全部 >cd>ls >之后桌面找到文件夹内容...
    i得深刻方得S阅读 4,607评论 1 9
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,327评论 0 17
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,943评论 4 60
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,043评论 25 707
  • 前言 动画主要的目的是让UI的变动变柔和,增强用户体验。 优秀的动画让用户感觉不到页面的跳转、感觉一切操作很自然。...
    NSNil阅读 245评论 0 0