首页 > 系统 > iOS > 正文

iOS基于UITableView实现多层展开与收起

2020-07-26 02:55:00
字体:
来源:转载
供稿:网友

本文实例为大家分享了bleView多层展开与收起的具体代码,供大家参考,具体内容如下

规则要求:

  • 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 = 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添加下一层数据。添加数据方法如下:

/** 在指定位置插入要展示的数据 @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;}}

收起方法实现如下:

/** 删除要收起的数据 @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;}

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

/** 与点击同一层的数据比较,然后删除要收起的数据和插入要展开的数据 @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。

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

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方法中实现点击展开与收起,方法如下:

- (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];}

效果

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持武林网。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表