本文实例为大家分享了bleView多层展开与收起的具体代码,供大家参考,具体内容如下
规则要求:
- tableview 有多层,类似于xcode文件目录的层级关系,每一个最开始展示的层姑且称之为根目录吧,并且,每个根目录下的层数不定。
- 与文件目录类似,每个目录下可以有不同层级的目录同时展开,但是同一层次中只有一层是展开的,即要展开B层次的某一层,则需要收起B层次所有其他的层级。
- 最底层是一个个文件,不能再展开(这里在业务逻辑上用处是:跳转到不同的页面)。
想法:
- 整个界面是一个tableview,层级关系用cell中的label的位置展现,而tableview的数据源是一个一维数组_resultArray,其中,展开是在特定位置插入数据,收起是在特定位置删除数据。
- 每一层目录存储着下一层的引用,就是包含了下一层的全部数据。展开该层的时候就是将下一层的数据加入_resultArray,收起该层时,是将该层的所有下层的数据从_resultArray中删除。
数据存储
1
2
3
4
5
6
7
8
9
|
//每个目录的数据结构如下: @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上下载并加入到项目中)进行显式转化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#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。建造过程如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
- ( 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 = 0; i < FourTitleArray.count; i++) { OpenTest *model = [[OpenTest alloc] init]; model.title = FourTitleArray[i]; model.level = 3; model.isOpen = NO; [fourArray1 addObject:model]; } //第三层数据 for ( int i = 0; i < ThreeTitleArray.count; i++) { OpenTest *model = [[OpenTest alloc] init]; model.title = ThreeTitleArray[i]; model.level = 2; model.isOpen = NO; model.detailArray = fourArray1; [threeArray1 addObject:model]; } //第二层数据 for ( int i = 0; i < SecondTitleArray.count; i++) { OpenTest *model = [[OpenTest alloc] init]; model.title = SecondTitleArray[i]; model.level = 1; model.isOpen = NO; model.detailArray = [threeArray1 mutableCopy]; [secondArray1 addObject:model]; } //第一层数据 for ( int i = 0; i < FirstTitleArray.count; i++) { OpenTest *model = [[OpenTest alloc] init]; model.title = FirstTitleArray[i]; model.level = 0; 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 > 0) { [self dealWithDataArray:model.detailArray]; } } } |
当首层没有展开数据时,点击首层展开第二层数据,比较容易实现,即在_resultArray添加下一层数据。添加数据方法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
/** 在指定位置插入要展示的数据 @param dataArray 数据数组 @param row 需要插入的数组下标 */ - ( void )addObjectWithDataArray:(NSMutableArray *)dataArray row:(NSInteger)row { for ( int i = 0; i < dataArray.count; i++) { OpenTest *model = dataArray[i]; model.isOpen = NO; [_resultArray insertObject:model atIndex:row]; row += 1; } } |
收起方法实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
/** 删除要收起的数据 @param dataArray 数据 @param count 统计删除数据的个数 @return 删除数据的个数 */ - (CGFloat)deleteObjectWithDataArray:(NSMutableArray *)dataArray count:(NSInteger)count { for (OpenTest *model in dataArray) { count += 1; if (model.isOpen && model.detailArray.count > 0) { count = [self deleteObjectWithDataArray:model.detailArray count:count]; } model.isOpen = NO; [_resultArray removeObject:model]; } return count; } |
在已经展开的时候点击另外一个目录,要先收起再展开。因为每个层次只有一个目录是展开的,所以收起的时候,只需要跟同层次的目录数据比较,如果是已经打开的,则删除打开目录的所有子层。方法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
/** 与点击同一层的数据比较,然后删除要收起的数据和插入要展开的数据 @param model 点击的cell对应的model @param row 点击的在tableview的indexPath.row,也对应_resultArray的下标 */ - ( void )compareSameLevelWithModel:(OpenTest *)model row:(NSInteger)row { NSInteger count = 0; NSInteger index = 0; //需要收起的起始位置 //如果直接用_resultArray,在for循环为完成之前,_resultArray会发生改变,使程序崩溃。 NSMutableArray *copyArray = [_resultArray mutableCopy]; for ( int i = 0; 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 + 1]; } |
界面
系统的tableviewcell 无法修改textLabel的位置,需要修改偏移量有两种方法。
1、继承UITableViewCell, 然后重写父类的方法 - layoutSubviews, 在该方法中修改textLabel的frame。
2、在cell.contentView 中添加一个label。
我这里使用的是第二种方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@ "Cell" ]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@ "Cell" ]; cell.selectionStyle = UITableViewCellSelectionStyleNone; UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(15, 0, UI_SCREEN_WIDTH / 2, 32)]; label.font = [UIFont systemFontOfSize:14]; 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(15 + (model.level - 1) * SpaceWidth , 0, UI_SCREEN_WIDTH / 2, 32); } } |
最后在didSelectRowAtIndexPath方法中实现点击展开与收起,方法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
- ( 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:0]; } else { if (model.detailArray.count > 0) { //原来是收起的,现在要展开,则需要将同层次展开的收起,然后再展开 [self compareSameLevelWithModel:model row:row]; } else { //点击的是最后一层数据,跳转到别的界面 NSLog(@ "最后一层" ); } } model.isOpen = !model.isOpen; //滑动到屏幕顶部 for ( int i = 0; i < _resultArray.count; i++) { OpenTest *openModel = _resultArray[i]; if (openModel.isOpen && openModel.level == 0) { //将点击的cell滑动到屏幕顶部 NSIndexPath *selectedPath = [NSIndexPath indexPathForRow:i inSection:0]; [tableView scrollToRowAtIndexPath:selectedPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; } } [tableView reloadData]; } |
效果
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。