提起笔,却不知道从何写起了,今天一整天都耗费在了这个可能根本不算是问题的小问题上,至今仍有一种蛋蛋的忧桑。。(噢,不是提笔,是键盘手T_T)
表格视图在项目中就像是每日的家常便饭,在cell上添加侧滑删除功能这种需求也是遍地可见。而就是这么一个家常菜却坑了我一天,可能我是真的闲的蛋疼吧,好吧,其实,讲道理还是我太菜,人艰不拆。
好了废话不多说,运用系统自带的API实现侧滑删除功能其实非常简单:
//- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{//
// if (editingStyle == UITableViewCellEditingStyleDelete) {
//
// 删除数据源对应模型
// [self.shops removeObjectAtIndex:indexPath.row];
// 从tableView中删除
// [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil] withRowAnimation:UITableViewRowAnimationTop];
// }
//
//}
只要重写上述代理方法,实现删除模式的相关处理即可,当然你也可以加一个else语句对于编辑模式不是删除时做一些其他的处理,比如控制台提示,或者是处理其它模式,比如插入insert,并且上述代码也可以不从tableView中删除,直接从数据源中删除,然后刷新表格就好了[tableView reloadData];这里我个人认为苹果的deleteRowsAtIndexPaths:方法肯定是有它的好处的,试想,数据很多的话,我们为了删除tableView中的一行,而刷新整个表格,这样真的好吗?另,上述只是最简单的处理情况,倘若数据很多,本地有数据库存储,在上述基础上还要进行数据库的删除和存储操作,同样的,如果数据源是从服务器获取的,那么还要相关请求删除服务器上的数据,不然下次来到该界面,删掉的数据又会显示。
从iOS8开始,苹果开放了这样一个API:
- (nullable NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath
返回一个UITableViewRowAction数组,每一个"Action"代表一个侧滑删除的Button。这样侧滑每一行Cell可以有更多按钮提供给用户交互。
不幸地是这个API只在iOS8才有,这样iOS8以下就没办法使用到这种效果。这种情况下我们不得不使用第三方库或者自己重写UITableViewCell来“模拟”出这种效果。那其实呢,我今天所要得需求是最基本的只有删除就OK的,但是我是自定义的cell,然后我遇到一个问题,就是滑动cell,删除按钮很难出现,十次能不能滑出一次还是个问题。
一般情况下只要你重写了- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {}方法,不实现也可以滑出按钮。那对于这样一个简单地需求我们完全可以自定义一个cell去实现它,思路很简单,可以在系统的UITableViewCell基础上添加一个ScrollView,再添加需要的菜单按钮,或者通过在系统的cell上添加滑动手势,监听来实现。我今天浪费了很多时间,其中有一个原因是原本我想自己封装一套的,结果发现要想真正写好一个框架其实并非易事,看似简单地功能需求,你要考虑的问题可能会很多。
我最后选择了使用第三方框架, 找了几个对比看了一下, https://github.com/MortimerGoro/MGSwipeTableCell 这个算是封装的比较成熟的了,支持多种侧滑方式以及立体等各种效果 。他里面封装了MGSwipeTableCell和MGSwipeButton,你只需要让你的cell继承于MGSwipeTableCell,然后像这样在tableView的数据源方法里面,创建cell的同时,给它配置侧滑菜单需要的buttons数组传给它就行。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString * reuseIdentifier = @"programmaticCell"; MGSwipeTableCell * cell = [self.tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
if (!cell) {
cell = [[MGSwipeTableCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:reuseIdentifier]; }
cell.textLabel.text = @"Title"; cell.detailTextLabel.text = @"Detail text"; cell.delegate = self;
//optional
//configure left buttons
cell.leftButtons = @[[MGSwipeButton buttonWithTitle:@"" icon:[UIImage imageNamed:@"check.png"] backgroundColor:[UIColor greenColor]], [MGSwipeButton buttonWithTitle:@"" icon:[UIImage imageNamed:@"fav.png"] backgroundColor:[UIColor blueColor]]];
cell.leftSwipeSettings.transition = MGSwipeTransition3D;
//configure right buttons
cell.rightButtons = @[[MGSwipeButton buttonWithTitle:@"Delete" backgroundColor:[UIColor redColor]], [MGSwipeButton buttonWithTitle:@"More" backgroundColor:[UIColor lightGrayColor]]];
cell.rightSwipeSettings.transition = MGSwipeTransition3D;
return cell;}
监听菜单里面button的点击事件,作者提供了两种选择,一种就是在上述代码中设置cell的代理,实现它的一些代理方法;另一种更方便就MGSwipeButton
里提供了一个block回调,你可以在初始化MGSwipeButton的时候添加它
[MGSwipeButton buttonWithTitle:@"More" backgroundColor:[UIColor lightGrayColor] callback:^BOOL(MGSwipeTableCell *sender) { NSLog(@"Convenience callback for swipe buttons!");}]
他提供的接口非常完善,具体的可以在MGSwipeTableCell和MGSwipeButton的.h文件里面看。我今天选择的就是使用block,一开始遇到的问题见如下代码,注释
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MyShopCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
cell.shop = self.shops[indexPath.row];
cell.myDelegate = self;
cell.rightButtons = @[[MGSwipeButton buttonWithTitle:@"删除" backgroundColor:[UIColor magentaColor] padding:50 callback:^BOOL(MGSwipeTableCell *sender) {
[self.shops removeObjectAtIndex:indexPath.row]; NSLog(@"%d-------%@",self.shops.count ,self.shops);
// 当我用这个方法删除时,偶尔会崩溃,具体原因不明 测试了很多遍,感觉是删太快就会出现,也可能是快速点击两次删除会出现,总之很奇怪,慢慢地一个一个删一般不会有问题(总之,用户正常使用的话,不影响,当时也没强行深究原因,暂且记下一笔)
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil] withRowAnimation:UITableViewRowAnimationTop];
//当我用这个方法删除时,好像没有什么问题,问题出在哪呢?(再后来发现好像也有崩溃
// [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:indexPath.row inSection:0]] withRowAnimation:UITableViewRowAnimationNone];
//不用删除方法,直接强行刷新表格,好像没出过bug
// [tableView reloadData]; return true;
}],
];
cell.rightSwipeSettings.transition = MGSwipeTransition3D;
return cell;
}
因为这是个概率性出现的bug,我这种菜鸟,在那无脑的尝试,瞎捣腾,试了度娘上的各种事实证明并不相关的方法,后来我干脆自己胡乱猜想,我当时还曾以为是线程里面的数据冲突,崩溃就崩在[self.shops removeObjectAtIndex:indexPath.row];这句上,就是说可能是我删除的indexPath.row时候那一行已经删除了,我又猜想是不是delete的时候读数据跟写数据冲突,我给block里面所有跟控制器有关的指针的__weak关键字,我甚至煞笔地给tableView的数据源方法加了锁,同时给我block里面删除操作都lock起来,最后把自己都锁煞笔了(其实,我对锁的使用并不熟练,而且我只在多线程里面偶有用到)总之一下午倒腾总结起来就一个字:然并卵!
好了,废话不多了,我这日记写的越来越像博客了0.0.。。。。
最后:
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MyShopCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
cell.shop = self.shops[indexPath.row];
cell.myDelegate = self;
__weak typeof(self) weakSelf = self;
cell.rightButtons = @[[MGSwipeButton buttonWithTitle:@"删除" backgroundColor:[UIColor magentaColor] padding:50 callback:^BOOL(MGSwipeTableCell *sender) {
}]
];
之前我在这个数据源方法里面写的,block里面使用的indexPath都是数据源方法
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath中传进来的参数
[weakSelf.shops removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationTop];
然后我仔细一看发现第三方的哪个cell 叫我传得button数组 button后面的方法里面 传的block回调有个参数sender 把cell 传进来了,我用lldb命令打印了一下 发现 两个indexPath 并不一样
因为是第一次用这个框架,一开始我还奇怪他block 为什么要传个sender 我在数据源方法里面本来能拿到,不是多余的吗 然后我刚刚脑子一热就试了下 发现没有崩溃了。但是不知道 为什么偶尔会崩,没崩的时候两者indexPath是一样的 ,我又猜想应该是跟cell的重用机制有关吧。具体确定的原因尚不明确,不过好在问题总算解决了,哎,还有有种蛋蛋的忧伤 ,废了一整天 解决了这么一个小问题还没搞清楚本质。
博文在此,日后必会解决地一清二楚,暂且记下,问题很小,我很菜,跟我一样的新手遇到了可以避免这坑,大神误入请略过。。。。