在之前做的一个项目中,有一个功能需求是要将递归的json数据,以多级列表的形式展示,并能随意展开,收回。在实现该功能的时候遇到的最大的问题就是对数据的解析,以及数据之间的层级关系的处理。最近想到这个功能,当时是受到某个开源项目的启发,现在自己重新梳理一下思路,加深一下记忆。
一、数据的处理
首先首先说一下对数据的处理。由于从服务器拿到的数据,是递归json数据,而且每个列表中的层级都不确定,有的只有一层、两侧数据,而有的却是多层数据,那这样的数据我们该怎么转换成模型呢?!我的解决方法,用个不太恰当的词形容叫“以牙还牙”,既然你用递归返回数据,那么,我也用同样的方法处理数据。下面看一下具体的实现demo:
1、从服务器上拿下来的数据大概是这个样子的:
@{@"list": @[@{@"name": @"first",@"id": @1,
@"subList":@[@{@"name": @"second",@"id": @2},
@{@"name": @"second",@"id": @2}]},
@{@"name": @"first",@"id": @1,
@"subList": @[@{@"name": @"second",@"id": @2,
@"subList": @[@{@"name": @"third",@"id": @3},
@{@"name": @"third",@"id": @3}]},
@{@"name": @"second",@"id": @2,
@"subList": @[@{@"name": @"third",@"id": @3},
@{@"name": @"third",@"id": @3}]}]},
@{@"name": @"first",@"id": @1,
@"subList": @[@{@"name": @"second",@"id": @2},
@{@"name": @"second",@"id": @2}]}]};
2、那么我们要怎么把数据字典转换成Model呢,首先先定义Model的属性和方法:
@interface ListModel : NSObject
@property(nonatomic,copy)NSString *name;
//@property(nonatomic,copy)NSString *nameId;
@property(nonatomic,copy)NSNumber *nameId;
@property(nonatomic,copy)NSArray<ListModel *>*subList;//子列表(注:子列表中可能还有子列表)
@property(nonatomic,assign)BOOL isExpand;//是否展开的标记
@property(nonatomic,weak)ListModel *superModel;//父类Model
@property(nonatomic,assign)int level;//深度,即Model所在列表的级数
- (instancetype)initWithDict:(NSDictionary *)dict;
+ (instancetype)listModelWithDict:(NSDictionary *)dict;
@end
3、接下来才是重点,下面我们来实现initWithDict方法,在该方法中,我们将会对递归数据进行处理,将数据转换成递归模型:
至此,对于数据的处理基本完成,当抓住重点之后,其实对于数据的处理思路还是很明显的。
二、封装tableView
1、首先我们先声明XTTreeTableView的初始化私有方法:
/**
创建XTTreeTableView的方法
@param frame tableView的位置
@param data tableView待展示的数据源
@return XTTreeTableView
*/
-(instancetype)initWithFrame:(CGRect)frame withData:(NSArray *)data;
接下来我们来看在XTTreeTableView.m中方法的实现。
2、我们现来声明XTTreeTableView的私有属性:
@property (nonatomic , strong) NSArray *data;//传递过来已经组织好的数据(最外层的Model)
@property (nonatomic , strong) NSMutableArray *tempData;//用于存储数据源(需要展示的数据)
3、下面我们来实现XTTreeTableView的创建方法:
- (instancetype)initWithFrame:(CGRect)frame withData:(NSArray *)data
{
self = [super initWithFrame:frame style:UITableViewStyleGrouped];
if (self) {
self.dataSource = self;
self.delegate = self;
_data = data;
_tempData = [NSMutableArray arrayWithArray:data];
}
return self;
}
初始化tableView之后,我们还要实现tableView的数据源方法:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.tempData.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CELL_ID = @"XTTree";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CELL_ID];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CELL_ID];
}
ListModel *model = self.tempData[indexPath.row];
cell.textLabel.text = model.name;
return cell;
}
4、下面我们来实现tableView的代理方法,这是重中之重,前面所以的准备都是为了这一功能。他实现思路是:当我们点击某一行cell时,首先我们拿到这一行对应的Model,判断该Model中的subList是否有subModel,如果有,则可以展开、收回,对是否展开属性取相反值parentModel.isExpand=!parentModel.isExpand;没有则不能展开、收回。
而对于可以展开、收回的,如果parentModel.isExpand=YES,即要展开列表,我们将subModel从subList中取出,插入到需要展示的数据源self.tempData中的相应位置,然后在tableView中的相应位置插入cell;
if (parentModel.isExpand) {//如果展开
for (int i = 0; i < parentModel.subList.count; i ++) {
ListModel *subModel = parentModel.subList[i];
[self.tempData insertObject:subModel atIndex:endPosition];
endPosition ++;
}
expand = YES;
}
如果'parentModel.isExpand=NO',即收回列表,我们将subList中的subModel从self.tempData中删除,然后将想应的cell从tableView中delete。
- (NSInteger)removeAllModelsAtSupModel:(ListModel *)supModel
{
NSInteger startPosition = [self.tempData indexOfObject:supModel];
CGFloat count;
for (int i = 0; i < supModel.subList.count; i ++) {
ListModel *model = supModel.subList[i];
count ++;
if (model.isExpand) {
count += model.subList.count;
model.isExpand = NO;
}
}
NSInteger endPosition = startPosition + count + 1;//supModel.subList.count + 1;
[self.tempData removeObjectsInRange:NSMakeRange(startPosition+1, count)];
return endPosition;
}
总结:
第一次写简书,为了加深自己的学习,有什么不足之处,还望大家多多指正。Demo:XTTreeTableView