规则要求:

  • tableview 有多层,类似于xcode文件目录的层级关系,每一个最开始展示的层姑且称之为根目录吧,并且,每个根目录下的层数不定。
  • 与文件目录类似,每个目录下可以有不同层级的目录同时展开,但是同一层次中只有一层是展开的,即要展开B层次的某一层,则需要收起B层次所有其他的层级。
  • 最底层是一个个文件,不能再展开(这里在业务逻辑上用处是:跳转到不同的页面)。

想法:

  • 整个界面是一个tableview,层级关系用cell中的label的位置展现,而tableview的数据源是一个一维数组_resultArray,其中,展开是在特定位置插入数据,收起是在特定位置删除数据。
  • 每一层目录存储着下一层的引用,就是包含了下一层的全部数据。展开该层的时候就是将下一层的数据加入_resultArray,收起该层时,是将该层的所有下层的数据从_resultArray中删除。

数据存储

//每个目录的数据结构如下:
@interface OpenTest : NSObject
@property (copy, nonatomic) NSString *title; //非首层展示的标题
@property (assign, nonatomic) NSInteger level; //决定偏移量大小
@property (copy, nonatomic) NSString *openUrl; //最后一层跳转的规则
@property (copy, nonatomic) NSMutableArray *detailArray; //下一层的数据
@property (assign, nonatomic) BOOL isOpen; //是否要展开
@property (copy, nonatomic) NSString *imageName; //首层的图片
@end

其中,因为detailArray中存储的对象也应该是OpenTest, 所以需要在OpenTest.m中借助MJExtension (在github上下载并加入到项目中)进行显式转化。

#import "OpenTest.h"
@implementation OpenTest
- (instancetype)init {
self = [super init];
if (self) {
[OpenTest mj_setupObjectClassInArray:^NSDictionary *{
return @{
@"detailArray" : [OpenTest class]
};
}];
}
return self;
}
@end

数据处理

开始建造源数据dataArray,该数组可明确层级关系,并且得到展示数组_resultArray。建造过程如下:

- (void)initData {
_dataArray = [NSMutableArray new];
_resultArray = [NSMutableArray new]; NSMutableArray *secondArray1 = [NSMutableArray new];
NSMutableArray *threeArray1 = [NSMutableArray new];
NSMutableArray *fourArray1 = [NSMutableArray new]; NSArray *FirstTitleArray = @[@"FirstTitle1", @"FirstTitle2", @"FirstTitle3", @"FirstTitle4", @"FirstTitle5", @"FirstTitle6", @"FirstTitle7", @"FirstTitle8", @"FirstTitle9", @"FirstTitle10"];
NSArray *SecondTitleArray = @[@"SecondTitle1", @"SecondTitle2", @"SecondTitle3"];
NSArray *ThreeTitleArray = @[@"ThreeTitle1", @"ThreeTitle2", @"ThreeTitle3", @"ThreeTitle4"];
NSArray *FourTitleArray = @[@"FourTitle1", @"FourTitle2", @"FourTitle3"];
NSArray *imageArray = @[@"scroller1", @"scroller2", @"scroller3", @"scroller4", @"scroller5", @"scroller6", @"scroller7", @"scroller8", @"scroller9", @"scroller10"]; //第四层数据
for (int i = ; i < FourTitleArray.count; i++) {
OpenTest *model = [[OpenTest alloc] init];
model.title = FourTitleArray[i];
model.level = ;
model.isOpen = NO; [fourArray1 addObject:model];
} //第三层数据
for (int i = ; i < ThreeTitleArray.count; i++) {
OpenTest *model = [[OpenTest alloc] init];
model.title = ThreeTitleArray[i];
model.level = ;
model.isOpen = NO;
model.detailArray = fourArray1; [threeArray1 addObject:model];
} //第二层数据
for (int i = ; i < SecondTitleArray.count; i++) {
OpenTest *model = [[OpenTest alloc] init];
model.title = SecondTitleArray[i];
model.level = ;
model.isOpen = NO;
model.detailArray = [threeArray1 mutableCopy]; [secondArray1 addObject:model];
} //第一层数据
for (int i = ; i < FirstTitleArray.count; i++) {
OpenTest *model = [[OpenTest alloc] init];
model.title = FirstTitleArray[i];
model.level = ;
model.isOpen = NO;
model.detailArray = [secondArray1 mutableCopy];
model.imageName = imageArray[i]; [_dataArray addObject:model];
} //处理源数据,获得展示数组_resultArray
[self dealWithDataArray:_dataArray];
} /**
将源数据数组处理成要展示的一维数组,最开始是展示首层的所有的数据
@param dataArray 源数据数组
*/
- (void)dealWithDataArray:(NSMutableArray *)dataArray {
for (OpenTest *model in dataArray) {
[_resultArray addObject:model]; if (model.isOpen && model.detailArray.count > ) {
[self dealWithDataArray:model.detailArray];
}
}
}

当首层没有展开数据时,点击首层展开第二层数据,比较容易实现,即在_resultArray添加下一层数据。添加数据方法如下:

/**
在指定位置插入要展示的数据
@param dataArray 数据数组
@param row 需要插入的数组下标
*/
- (void)addObjectWithDataArray:(NSMutableArray *)dataArray row:(NSInteger)row {
for (int i = ; i < dataArray.count; i++) {
OpenTest *model = dataArray[i];
model.isOpen = NO;
[_resultArray insertObject:model atIndex:row];
row += ;
}
}

收起方法实现如下:

/**
删除要收起的数据
@param dataArray 数据
@param count 统计删除数据的个数
@return 删除数据的个数
*/
- (CGFloat)deleteObjectWithDataArray:(NSMutableArray *)dataArray count:(NSInteger)count {
for (OpenTest *model in dataArray) {
count += ; if (model.isOpen && model.detailArray.count > ) {
count = [self deleteObjectWithDataArray:model.detailArray count:count];
} model.isOpen = NO; [_resultArray removeObject:model];
} return count;
}

在已经展开的时候点击另外一个目录,要先收起再展开。因为每个层次只有一个目录是展开的,所以收起的时候,只需要跟同层次的目录数据比较,如果是已经打开的,则删除打开目录的所有子层。方法如下:

/**
与点击同一层的数据比较,然后删除要收起的数据和插入要展开的数据
@param model 点击的cell对应的model
@param row 点击的在tableview的indexPath.row,也对应_resultArray的下标
*/
- (void)compareSameLevelWithModel:(OpenTest *)model row:(NSInteger)row {
NSInteger count = ;
NSInteger index = ; //需要收起的起始位置
//如果直接用_resultArray,在for循环为完成之前,_resultArray会发生改变,使程序崩溃。
NSMutableArray *copyArray = [_resultArray mutableCopy]; for (int i = ; i < copyArray.count; i++) {
OpenTest *openModel = copyArray[i];
if (openModel.level == model.level) {
//同一个层次的比较
if (openModel.isOpen) {
//删除openModel所有的下一层
count = [self deleteObjectWithDataArray:openModel.detailArray count:count];
index = i;
openModel.isOpen = NO;
break;
}
}
} //插入的位置在删除的位置的后面,则需要减去删除的数量。
if (row > index && row > count) {
row -= count;
} [self addObjectWithDataArray:model.detailArray row:row + ];
}

界面

系统的tableviewcell 无法修改textLabel的位置,需要修改偏移量有两种方法。
1、继承UITableViewCell, 然后重写父类的方法 - layoutSubviews, 在该方法中修改textLabel的frame。
2、在cell.contentView 中添加一个label。

我这里使用的是第二种方法:

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];

    if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];
cell.selectionStyle = UITableViewCellSelectionStyleNone; UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(, , UI_SCREEN_WIDTH / , )];
label.font = [UIFont systemFontOfSize:];
label.tag = LabelTag; [cell.contentView addSubview:label];
} for (UIView *view in cell.contentView.subviews) {
if (view.tag == LabelTag) {
((UILabel *)view).text = model.title;
((UILabel *)view).frame = CGRectMake( + (model.level - ) * SpaceWidth , , UI_SCREEN_WIDTH / , );
}
}

最后在didSelectRowAtIndexPath方法中实现点击展开与收起,方法如下:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSInteger row = indexPath.row;
OpenTest *model = _resultArray[row]; if (model.isOpen) {
//原来是展开的,现在要收起,则删除model.detailArray存储的数据
[self deleteObjectWithDataArray:model.detailArray count:];
}
else {
if (model.detailArray.count > ) {
//原来是收起的,现在要展开,则需要将同层次展开的收起,然后再展开
[self compareSameLevelWithModel:model row:row];
}
else {
//点击的是最后一层数据,跳转到别的界面
NSLog(@"最后一层");
}
} model.isOpen = !model.isOpen; //滑动到屏幕顶部
for (int i = ; i < _resultArray.count; i++) {
OpenTest *openModel = _resultArray[i]; if (openModel.isOpen && openModel.level == ) {
//将点击的cell滑动到屏幕顶部
NSIndexPath *selectedPath = [NSIndexPath indexPathForRow:i inSection:];
[tableView scrollToRowAtIndexPath:selectedPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
} [tableView reloadData];
}

效果

demo.png

UITableView多层展开与收起的更多相关文章

  1. ios知识点总结——UITableView的展开与收缩及横向Table

    UITableVIew是iOS开发中使用最为广泛的一种控件,对于UITableView的基本用法本文不做探讨,本文主要是针对UITableView的展开与收缩进行阐述,在文章的后面也会探讨一下横向ta ...

  2. 【Android】键盘的展开和收起

    键盘的展开和收起主要使用到类InputMethodManager:http://developer.android.com/reference/android/view/inputmethod/Inp ...

  3. js 点击展开、收起

    //点击展开.收起 window.onload=function(){ var current=document.getElementsByTagName('li')[0]; document.bod ...

  4. 长图的展开与收起(Android)

    前言: 在app的文章中,经常会夹杂着一些特别长的长图.在阅读的时候需要滑动很久才能看图片下方的文字,因此对于长图只展示图片上面一部分,并且可以展开这个功能是很重要的. 效果: 基本思路: 利用sca ...

  5. jQuery实现画面的展开、收起和停止

    主要用到动画效果中的三个操作 ("#id").slideDown(3000): // 后面的数字表示效果的时长 ("#id").stop(); ("# ...

  6. div展开与收起(鼠标点击)

    效果图: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF- ...

  7. Silverlight自定义控件系列 – TreeView (3) 添加展开和收起事件

    由于Writer嫌我文章过长,只能把上篇拆开两半了.以下是接着上篇的. 准备工作做完了,现在就要完成点击事件. 定义Expander和单击事件: 1: /// <summary> 2: / ...

  8. Vue:列表展开和收起(超过一定行数时显示‘查看更多’按钮)

    前言:前端小白记录的一些小功能~ 公司开发中的小程序中有做任务签到的功能,这就涉及到了任务列表以及对任务列表的展开和收起功能,好了可以开始了,说多了就烦了 1.首先是css样式,因为设计稿上是超过两行 ...

  9. iOS-RATreeView多层UITableViewCell展示【多级列表展开与收起】的使用

    1.前言 iOS开发时,经常接触到的列表展示就是Tableview再熟悉不过了,但是如果接触到多层多级cell的展示,用大牛Augustyniak写的RATreeView是最好不过的了,Git地址:h ...

随机推荐

  1. pku2104

    传送门:http://poj.org/problem?id=2104 题目大意:给定一个长度为N的数组{A[i]},你的任务是解决Q个询问.每次询问在A[l], A[l+1], ...... , A[ ...

  2. "SQLServer复制需要有实际的服务器名称才能连接到服务器,请指定实际的服务器名"转

     "SQLServer复制需要有实际的服务器名称才能连接到服务器,请指定实际的服务器名" 2014-06-12 12:01:10 最近在学习SQL SERVER的高级复制技术的时候 ...

  3. maven 第一次运行报错

    在大中国的网络环境下,使用一些国外的资源,是一件很痛苦的事情... 大概在好几个月以前,一个同事跟我说,没事的时候学习maven,现在公司项目都用这个做管理 还给了我电子书<Maven实战> ...

  4. iOS 开发新版 动态库framework

    0. 参考 http://www.cocoachina.com/industry/20140613/8810.html framework+xib参考 : http://blog.csdn.net/x ...

  5. jquery serialize 和 console 漫谈

    serialize   获取表单中所有的数据 今天刚刚发现这个神奇的东西,顿时感觉高大上了很多,比以前一个一个用 val()取出来值  ,然后再 ajax 方便多了 代码示例 $(document). ...

  6. 解决windows 10 9926 中vmware安装的虚拟机无法桥接上网的问题

    从windows 10 出来之后就安装了使用,但一直有一个问题直到9926也没有解决,那就是vmware下的虚拟机无法桥接上网,但NAT方式正常.发现有一种办法可以实现桥接方式上网.但这种方式下本机与 ...

  7. HDU-1994-利息计算

    题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=1994 水题 题目分析 就是两种储存方式,输出所得本金加利息 代码 #include<stdio. ...

  8. BNU Online Judge-34976-数细菌

    题目链接 http://www.bnuoj.com/bnuoj/problem_show.php?pid=34976 题目分析通过a b可以设x,y x+y=a    x+3*y=b  解出x,y, ...

  9. java实现算术表达式求值

    需要根据配置的表达式(例如:5+12*(3+5)/7.0)计算出相应的结果,因此使用java中的栈利用后缀表达式的方式实现该工具类. 后缀表达式就是将操作符放在操作数的后面展示的方式,例如:3+2 后 ...

  10. Spring 使用context:annotation-config的设置

    Spring 使用context:annotation-config的设置: 还是需要声明Bean的,并且还可能自己定义Annotation: xml: <?xml version=" ...