1、前言
接下来,我们将开始搭建时间线界面。该模块是界面展示中最大的难点--时间线布局。那么,我们先来看看效果图,因为gif上传后,动不了。所以在这里用几张截图和文字简单的描述一下,具体效果大家可下载项目自行查看~
这里写图片描述这里写图片描述
从图一滚动到图二时,头部从7月的数据变成6月的账单数据。
2、时间线搭建
2.1、前言
这个时间线界面是仿照口袋记账的,一开始没有头绪的时候,就把自己的手机越狱了,然后通过Reveal进行查看其布局,具体Reveal的用法,可以看我之前的博客。传送门
2.2、Cell的设计
先看一下,这个界面的结构,注意View标注的文字,下文说明会用到的。 这里写图片描述从上图不难看出,一共分为两种Cell:
- 显示账单信息的Cell,如红框所示,显示账单类型,金额
- 显示当天日期的一个汇总,如蓝框所示,显示该日的一个总收入和总支出
3、数据准备
3.1、模型设计
由于我们的Cell是有两种类型的,那么我们需要通过模型去控制Cell的产生以及赋值。因此,我们需要一个type来区分是什么类型的Cell,通过Type去加载对应的Cell。模型设计如下:
红框Cell模型
/// 时间线模型
@interface MPTimeLineModel : NSObject
/// 模型的类型
@property (nonatomic, assign) TimeLineType type;
/// 账单模型
@property (nonatomic, strong) MPBillModel *bill;
/// 时间字符串
@property (nonatomic, copy) NSString *dateStr;
/// 一天内的账单模型
@property (nonatomic, strong) MPDayBillModel *dayBill;
@end
蓝框Cell模型
/// 一天的消费模型
@interface MPDayBillModel : NSObject
/// 日期字符串
@property (nonatomic, copy) NSString *dateStr;
/// 收入
@property (nonatomic, assign) double income;
///支出
@property (nonatomic, assign) double outcome;
@end
3.2、数据查询
由时间线的布局特性,要求我们要以“dateStr”字段进行进行降序排序(即最新的日期放在最前面)。然后再以“日”为单位,将同一日的账单的归类在一起,整合成MPDayBillModel模型。
一、查询当前账本的所有账单并排序
MPBillManager下的方法
- (RLMResults *)getBillsInCurrentBook
{
MPBookModel *book = [[MPBookManager shareManager] getCurrentBook];
RLMResults *results = [MPBillModel objectsWhere:@"book=%@", book];
// 返回排序后的结果集
return [self sortTheResultsByDate:results];
}
- (RLMResults *)sortTheResultsByDate:(RLMResults *)results
{
// 首先根据dateStr(账单时间)进行排序
RLMSortDescriptor *desc1 = [RLMSortDescriptor sortDescriptorWithKeyPath:@"dateStr" ascending:NO];
// 再根据recordDate(记录时间)进行排序
RLMSortDescriptor *desc2 = [RLMSortDescriptor sortDescriptorWithKeyPath:@"recordDate" ascending:NO];
return [results sortedResultsUsingDescriptors:@[desc1, desc2]];
}
二、以“日”为单位,开始整合数据模型,这里的实现有点繁琐,但是逻辑是不难的。核心思路是找到同一天的所有账单后,创建一个MPDayBillModel插入。以此类推,那么实现代码如下:
MPTimeLineModel的类方法
+ (NSMutableArray *)timeLineArrayWithResults:(RLMResults *)results
{
NSMutableArray *modelArray = [NSMutableArray array];
NSMutableArray *billInSameDay = [NSMutableArray array];
for(int i = 0; i < results.count; i++)
{
MPBillModel *bill = results[i];
// 当数组为空时,直接添加元素
if(billInSameDay.count == 0)
{
[billInSameDay addObject:bill];
}
else
{
// 将日期相同的账单,放在同一个数组中
MPBillModel *lastOj = billInSameDay.lastObject;
if([bill.dateStr isEqualToString:lastOj.dateStr])
{
[billInSameDay addObject:bill];
}
else
{
// 创建Day类型的模型
[modelArray addObject:[self getDayItemWithBillArray:billInSameDay dateStr:lastOj.dateStr]];
// 生成Normal类型的模型
for (MPBillModel *bill in billInSameDay)
{
MPTimeLineModel *model = [[MPTimeLineModel alloc] init];
model.bill = bill;
model.type = TimeLineNormalItem;
[modelArray addObject:model];
}
// 重新开始分类
[billInSameDay removeAllObjects];
[billInSameDay addObject:bill];
}
}
}
if(billInSameDay.count != 0)
{
MPBillModel *bill = billInSameDay.firstObject;
[modelArray addObject:[self getDayItemWithBillArray:billInSameDay dateStr:bill.dateStr]];
// 生成Normal类型的模型
for (MPBillModel *bill in billInSameDay)
{
MPTimeLineModel *model = [[MPTimeLineModel alloc] init];
model.bill = bill;
model.type = TimeLineNormalItem;
[modelArray addObject:model];
}
}
return modelArray;
}
4、头部数据的切换
4.1、核心思路
这里有一个很重要的效果,就是当6月的节点滑动到头部时,头部的header将显示6月的总收入以及总支出数据。那么,我们就需要在月份节点与头部View相交的时候,做数据复制。那么,沿着这个思路,我的解决方案就是,首先将月份节点,头部View转为同一坐标系,然后通过判断是否相进行处理。
4.2、添加监听的位置
自然地,我们需要在scrollView滚动的时候进行实时监听。此外,当scrollView快速滚动时,scrollViewDidScroll调用的不够频繁,因此里面计算header数据需要在滚动结束时,需要判断是否切换了月份。
所以我们需要在以下两个方法进行判断:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
判断的方式稍有不同,具体的实现方式请看MPBillTableViewController的上述的两个scrollView代理方法
5、总结
该时间线的实现共有两个难点:
- 难点一,数据查询后,生成业务需求的模型
- 难点二,头部View的数据监听
但只要掌握其核心思路,再去阅读代码,我相信大家都能看的懂的~
github地址
https://github.com/maple1994/MPTally
请顺手给一个start哦,哈哈
网友评论