欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2024-02-15 19:38:10
本文实例为大家分享了bleview多层展开与收起的具体代码,供大家参考,具体内容如下 规则要求: tableview 有多层,类似于xcode文件目录的层级关系...

本文实例为大家分享了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];
}


效果

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

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