表格视图的展开与收缩,就像腾讯QQ的好友列表的功能一样在开发中我们常常用到。实现的思路有两个:
1.定义一个分组的tableView,在头部视图上添加tap手势触发展开与收缩的事件,声明bool值或者标识符记录展开与收缩的状态。当 tableView展开的时候,刷新那一组的cell高度为大于0的数值,那么就展开了。当tableView收缩的时候,刷新那一组的cell高度等于0,那么就收缩了。
2.定义一个分组的tableView,在头部视图上添加tap手势触发展开与收缩的事件,声明bool值或者标识符记录展开与收缩的状态。当 tableView展开的时候,使用关键代码[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationTop],(就是在相应的分组当中插入cell)那么就展开了。当tableView收缩的时候,使用关键代码[self.listTable deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationBottom],(也就是在相应的分组中删除所有的cell)那么就收缩了。
2021年10月08日更新:事隔三年多之后,再次看到了自己写的这篇文章,更新一下,发现当年是真的菜。现在提供一个更为方便、安全、简单的方法.
主体思路不会发生变化,就是在展开和收缩的时候,提供一个简便的方法.在-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section这个协议方法里面做文章,如果这一组处于收缩状态的时候numberOfRowsInSection直接返回0行,这不就收缩了。如果这一组处于展开状态的话就返回数据源的行数量,这样我们就不需要对数据源进行操作了,我们可以在数据模型中声明Bool值属性.
第一种方法如下:
#import "ViewController.h"
#import "SectionHeadView.h"
#import "CustomerTableViewCell.h"
define COLOR_RGB(_R,_G,_B,_A) [UIColor colorWithRed:_R/255.0 green:_G/255.0 blue:_B/255.0 alpha:_A]
@interface ViewController ()<UITableViewDelegate,UITableViewDataSource>{
NSArray *_dataSource;
NSArray *shopNameArr;
NSMutableArray *openArr;
}
@property (nonatomic, strong)UITableView *myTableView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self getSources];
[self initalizeUserInterface];
}
- (void)getSources{
_dataSource = @[@[@"周为",@"李超",@"周为",@"李超",@"张路",@"12",@"周为",@"李超",@"张路",@"12",@"张路",@"12",@"34",@"56",@"78",@"910"],@[@"唐进",@"周为",@"李超",@"张路",@"12",@"刘虎",@"李源",@"樊亮",@"周为",@"李超",@"张路",@"12",@"周为",@"李超",@"张路",@"12"],@[@"周心云",@"周为",@"李超",@"张路",@"12",@"周为",@"李超",@"张路",@"12",@"周为",@"李超",@"张路",@"12",@"周为",@"李超",@"张路",@"12",@"周为",@"李超",@"张路",@"12"]];
shopNameArr = @[@"我的室友",@"睿峰朋友",@"班主任"];
openArr = [@[] mutableCopy];
for (int i = 0; i < shopNameArr.count; i++) {
[openArr addObject:@"0"];
}//记录展开状态
}
- (void)initalizeUserInterface{
self.automaticallyAdjustsScrollViewInsets =NO;
[self.view addSubview:self.myTableView];
}
//设置多少组
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return _dataSource.count;
}
//设置每组多少行
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSArray *arr = _dataSource[section];
return arr.count;
}
//设置每行多高
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([[openArr objectAtIndex:indexPath.section]isEqual:@"0"]) {
return 0;
}else{
return 45;
}
}
//调整section头部的间距
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 30;
}
// 自定义头部视图
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
//重用头部视图
static NSString *HeadViewIdentifier = @"HeadViewIdentifier";
SectionHeadView *headView = [[SectionHeadView alloc]initWithReuseIdentifier:HeadViewIdentifier];
headView.realShopName.text = [shopNameArr objectAtIndex:section];
headView.rightImageView.image = [UIImage imageNamed:@"catalogs_arrow@2x.png"];//给右边图片
//使用block修饰
__block SectionHeadView *weakHeadView = headView;
headView.TapCallBack = ^(void){
//NSLog(@"回调组的点击手势:%@",[shopNameArr objectAtIndex:section]);
if ([[openArr objectAtIndex:section]isEqual:@"0"]) {
[openArr setObject:@"1" atIndexedSubscript:section];
[UIView animateWithDuration:0.4 animations:^{
weakHeadView.rightImageView.transform = CGAffineTransformMakeRotation(M_PI);
} completion:^(BOOL finished) {
//展开的时候为了防止右边尖头动画执行完毕再弹回来,我们这里刷新的是当前组下面的cell
NSMutableArray *arr = [@[] mutableCopy];
NSArray *brr = _dataSource[section];
for (int i = 0; i < brr.count; i++) {
[arr addObject:[NSIndexPath indexPathForRow:i inSection:section]];
}
//刷新特定的cell
[self.myTableView reloadRowsAtIndexPaths:arr withRowAnimation:UITableViewRowAnimationRight];
}];
}else if ([[openArr objectAtIndex:section]isEqual:@"1"]){
[openArr setObject:@"0" atIndexedSubscript:section];
[UIView animateWithDuration:0.4 animations:^{
weakHeadView.rightImageView.transform = CGAffineTransformMakeRotation(0);
} completion:^(BOOL finished) {
//刷新某一组
[self.myTableView reloadSections:[NSIndexSet indexSetWithIndex:section] withRowAnimation:UITableViewRowAnimationLeft];
}];
}
};
return headView;
}
//处理删除逻辑
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{
if (editingStyle != UITableViewCellEditingStyleDelete) {
//如果不是删除状态直接返回
return;
}
NSLog(@"你真的删除了我");
//将编辑状态置为NO,一定要设置为NO才会有那个动画效果
[_myTableView setEditing:NO animated:YES];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"HomeCell";
CustomerTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[CustomerTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;/
cell.layer.masksToBounds = YES;//设置超出父视图的部分不显示,不设置会有出现重叠的问题
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.nameLabel.text = _dataSource[indexPath.section][indexPath.row];
return cell;
}
-(UITableView *)myTableView
{
if (_myTableView == nil) {
_myTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 64,375,603) style:UITableViewStylePlain];
_myTableView.delegate = self;
_myTableView.dataSource = self;
// _myTableView.backgroundColor = COLOR_RGB(239, 239, 244, 1);
_myTableView.separatorColor = [UIColor redColor];
_myTableView.sectionFooterHeight = 0.0;
//设置为不显示分割线过后,组上的分割线也会不显示
_myTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
}
return _myTableView;
}
}
第二种方法:这种方法更为高大上,效率也更高,更规范,推荐使用这种。- (void)cellInsertOrDelete:(BOOL)insert是一个点击分组头部视图的响应事件,参数为一个bool值用于判断是展开还是收缩。
// 表格视图的删除与插入,删除就是收缩,插入就是展开。
- (void)cellInsertOrDelete:(BOOL)insert{
// 1. 这个方法用于在调用插入,删除,选择方法时,同时有动画效果。
// 2. 用endUpdate能动画改变行高,而无需relaod这个cell。
// 3. beginUpdate和endUpdate成对使用,其包含的block里面,如果没有插入删除,选择的方法被使用。有可能导致这个table view的一些属性失效,例如行的数量。
// 4. 不应该在这个block范围里调用 reloadData,或者reloadRowsAtIndexPaths。一旦使用,必须自己执行和管理自己的动画效果。
// 第三点和第四点比较重要。也是导致闪退的原因。reloadData会引起,获取单元格高度,以及cell的重新加载。这会导致一些动画对应的行号产生变化。从而闪退。
// beginUpdates方法和endUpdates方法是什么呢?
//
// 这两个方法,是配合起来使用的,标记了一个tableView的动画块。
//
// 分别代表动画的开始开始和结束。
//
// 两者成对出现,可以嵌套使用。
//
// 一般,在添加,删除,选择 tableView中使用,并实现动画效果。
//
// 在动画块内,不建议使用reloadData方法,如果使用,会影响动画。
// 一般在UITableView执行:删除行,插入行,删除分组,插入分组时,使用!用来协调UITableView的动画效果。
[self.listTable beginUpdates];
NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:self.datas.count];
[self.datas enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSIndexPath *indexP = [NSIndexPath indexPathForRow:idx inSection:0];
[indexPaths addObject:indexP];
}];
if (insert) {
[self.listTable insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationTop];
}else{
[self.listTable deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationBottom];
}
[self.listTable endUpdates];
}
再次解释下:
/*
UITableView的 beginUpdates方法和endUpdates方法是什么呢?
这两个方法,是配合起来使用的,标记了一个tableView的动画块。
分别代表动画的开始开始和结束。
两者成对出现,可以嵌套使用。
一般,在添加,删除,选择 tableView中使用,并实现动画效果。
在动画块内,不建议使用reloadData方法,如果使用,会影响动画。
如果我们的UITableView是分组的时候,我们如果删除某个分组的最后一条记录时,相应的分组也将被删除。所以,必须保证UITableView的分组,和cell同时被删除。
所以,就需要使用beginUpdates方法和endUpdates方法,将要做的删除操作“包”起来。
*/