iOS基于UITableView实现多层展开与收起
发布时间 - 2026-01-10 23:21:19 点击率:次本文实例为大家分享了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多层展开与收起
# UITableView展开与收起
# UITableView多层展开
# iOS中Cell的Section展开和收起的示例代码
# iOS中的二级菜单及Cell的展开收起示例
# 是一个
# 首层
# 下一层
# 的是
# 是在
# 方法如下
# 则需
# 跳转到
# 第二层
# 偏移量
# 只需
# 其他的
# 数据结构
# 然后再
# 数据处理
# 只有一个
# 重写
# 种方法
# 另外一个
# 跳转
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
长沙做网站要多少钱,长沙国安网络怎么样?
如何快速搭建安全的FTP站点?
香港服务器WordPress建站指南:SEO优化与高效部署策略
Win11怎么修改DNS服务器 Win11设置DNS加速网络【指南】
Win11任务栏卡死怎么办 Windows11任务栏无反应解决方法【教程】
LinuxCD持续部署教程_自动发布与回滚机制
如何快速搭建高效香港服务器网站?
阿里云网站搭建费用解析:服务器价格与建站成本优化指南
Laravel如何实现API版本控制_Laravel版本化API设计方案
Laravel怎么连接多个数据库_Laravel多数据库连接配置
Laravel Sail是什么_基于Docker的Laravel本地开发环境Sail入门
Android自定义listview布局实现上拉加载下拉刷新功能
java获取注册ip实例
香港服务器网站推广:SEO优化与外贸独立站搭建策略
如何快速查询网站的真实建站时间?
php后缀怎么变mp4格式错误_修改扩展名提示格式不对怎么办【技巧】
Laravel如何使用Blade组件和插槽?(Component代码示例)
Laravel如何实现数据库事务?(DB Facade示例)
如何在云虚拟主机上快速搭建个人网站?
详解jQuery中基本的动画方法
浏览器如何快速切换搜索引擎_在地址栏使用不同搜索引擎【搜索】
Laravel如何使用集合(Collections)进行数据处理_Laravel Collection常用方法与技巧
如何用低价快速搭建高质量网站?
如何用搬瓦工VPS快速搭建个人网站?
千问怎样用提示词获取健康建议_千问健康类提示词注意事项【指南】
Laravel如何实现本地化和多语言支持_Laravel多语言配置与翻译文件管理
高端建站如何打造兼具美学与转化的品牌官网?
Python数据仓库与ETL构建实战_Airflow调度流程详解
阿里云高弹*务器配置方案|支持分布式架构与多节点部署
Laravel如何实现一对一模型关联?(Eloquent示例)
JS实现鼠标移上去显示图片或微信二维码
Laravel API路由如何设计_Laravel构建RESTful API的路由最佳实践
如何批量查询域名的建站时间记录?
如何快速生成ASP一键建站模板并优化安全性?
晋江文学城电脑版官网 晋江文学城网页版直接进入
Laravel如何处理文件下载请求?(Response示例)
如何快速打造个性化非模板自助建站?
Laravel如何使用Vite进行前端资源打包?(配置示例)
Android利用动画实现背景逐渐变暗
Laravel如何获取当前用户信息_Laravel Auth门面获取用户ID
如何用腾讯建站主机快速创建免费网站?
香港服务器选型指南:免备案配置与高效建站方案解析
Linux网络带宽限制_tc配置实践解析【教程】
微博html5版本怎么弄发超话_超话进入入口及发帖格式要求【教程】
VIVO手机上del键无效OnKeyListener不响应的原因及解决方法
Laravel如何安装Breeze扩展包_Laravel用户注册登录功能快速实现【流程】
详解vue.js组件化开发实践
详解Android图表 MPAndroidChart折线图
Android实现代码画虚线边框背景效果
Linux系统运维自动化项目教程_Ansible批量管理实战

