deleteSections & deleteRows 我踩得坑
需求背景
有这样一个需求,有一个用来展示商品的列表,你可以从别的数据源添加过来,能添加当然就能删除了,这时候就用到了UITableView/UICollextionView组或者cell的删除,但在测试的过程中发现这里会出现crash,然后在一个夜深人静的晚上安安静静的找了下原因,下面是我探究的结果来分享一下。
模拟一下
下面是一个简单的demo来模拟这个问题,大致的思路如下:(没用的代码没有粘贴出来 看关键点)
1、创建一个tablewView 在cell 上添加一个删除按钮 给cell 设置一个index的标记
2、点击删除 回调index 然后在数据源中按照index 找到数据 删除掉
3、执行deleteSections 或者 deleteRows 来看看下面的简单的代码,看能看出问题吗?
extension ViewController:UITableViewDelegate,UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
print("我来重新获取 tableView SectionsNumber")
return array.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("我来重新获取 tableView RowsNumber")
return 1
}
func tableView(_ tableView:UITableView,cellForRowAt indexPath:IndexPath) -> UITableViewCell {
print("我来重新获取 cell")
let cell:TabCell = tableView.dequeueReusableCell(withIdentifier: "Identifier", for: indexPath) as! TabCell
let str = array[indexPath.section]
cell.index = indexPath
cell.setdata(str)
cell.block = {(index) in
print(index.section)
///
self.array.remove(at: index.section)
self.tabview.beginUpdates()
self.tabview.deleteSections(IndexSet.init(arrayLiteral: index.section), with: UITableView.RowAnimation.automatic)
self.tabview.endUpdates()
}
return cell
}
}
/// Cell 代码
class TabCell: UITableViewCell {
var index:IndexPath?
var block:Block?
@objc func deleteClick() {
print("点击事件之后的打印--")
self.block!(self.index!)
}
func setdata(_ str:Int) {
title.text = String(str)
}
}
下面是删除的gif看看是否能顺利的删除完

删除到一半的时候crash了!看看crash的日志,分析一下问题:

数组越界了!通过这点我们能分析出下面几个结论:
1 、每次删除的时候都会重新去获取它的组数和组里面cell的个数。
2、不会重新走 cellForRowAt 所以我们给cell赋的index的值不会更新,所以删除某一个cell的时候。它拿到的index还是最开始赋值给它的index,上面这两点的原因造成了crash。
那分析到这一步,解决的办法也就有了,你删除完组或者cell之后重新reloaddata是能解决crash的,看看效果:

问题到了这里你可以说解决了,但也可以说没解决。要是不介意UI效果(仔细看他们之间的区别),要是不介意性能的问题(数据量不会大)就可以这样做,但像我这种比较追求UI效果,要是把App看做一个人的话那毫无疑问UI就是它的衣服,人靠衣装嘛,那我们还有别的方式去解决的这问题吗?
这时候我做了这样一个尝试,既然我们的index没有发生改变,那数据源呢?我么可以在它身上去做一些改变,在做改变之前我们还有一个问题需要去认识,说白了也是应为我们的index没有及时刷新引起的。
要是你再这样回调这个index做操作,然后删除数组元素中的某一位置的元素,保证和剩下的section个数是一样的,但是不刷新TableView ,会发生什么呢?看下面gif

我们删除了 6、5、4 在回去删除 8 的时候还是crash了,这时候我们的数据是这样处理的 self.array.remove(at: 0) 按道理,删除一组我就总数据源删除0位置的元素,这时候剩下的section 和我们数据源的个数是对应的,发生crash的原因呢?不知道有没有人这样想,因为我们在返回组数的时候是采用了数据源的个数,它们俩之间是一一对应的,按到离似乎是不应该有问题的,但还是crash了,我们看看日志。
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to delete section 7, but there are only 5 sections before the update'
这句话的说的意思就是我们尝试删除section 7 但在这之前我们的numberSection返回的组数却是 5 ,这就产生了一个crash 原因前面说了。还是indexSection 没法对应上的问题,或者说就是indexSection越界了。
我在网上有搜到这两者之间不匹配的问题,比如你不删除数据源,也就是没有 self.array.remove(at: 0) ,你直接删除一组,当然你返回组数的时候还是返回 self.array.count 这时候又会是什么问题呢?
'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of sections. The number of sections contained in the table view after the update (8) must be equal to the number of sections contained in the table view before the update (8), plus or minus the number of sections inserted or deleted (0 inserted, 1 deleted)
这里你再理解一下,你删除之后按道理应该就剩7组了,但是在执行到返回组数的时候你的数据源返回的个数还是8,这里就是不匹配的问题,当然返回组个数是6也会crash,道理和我们这解释的相同,要是有同类型的错了就好好理解梳理一下,我们在做一些 update 操作的时候处理不好匹配问题也会经常遇到这个问题。
找一个方法解决
找一个办法解决这个问题,我们前面有说要是reloaddata一次就解决问题了,那我们在reloaddata最重要的操作或者目的是什么呢?那就是给我们回调回来的 index 一个不越界的正常的值,我们从这点出发,我们在不执行reloadata的情况下回调一个正常的index应该也能解决问题,那有什么办法回调一个正常的index呢?
其实也很简单,我们赋给cell的index我们可以在执行完删除之后自己重新组装一次!那怎么组装呢?这时候就要利用其我们传给 cell 的model了,我们传给cell 的model指向的还是我们数据源的model (swift引用类型。oc也是指针),并没有重新赋值,这时候我们就可以在 model 里面写一个 IndexPath 进去,然后在每一次删除完之后我们自己操作在数据源中重新排列这个model中的indexPath ,在删除点击回调的时候直接回调这个model ,在选择删除的时候我们也删除从model中获取到的idnex不就解决了我们的问题了嘛!
上面就是解决我们这问题的思路。代码其实也很简单,简单到不值得我们在写出了。下面是我们自己项目中我执行这一段逻辑自己的代码,帮助理清上面说的思路。
/// 删除一个选中的商品
/// - Parameter index: index description
func deleteGoods(indexModel:PPOrderGoodListModel,tableView:UITableView) { let index = indexModel.indexPath!
/// 部分退款 并且商品和凭证一对一的时候是按照组删除的 别的情况是按照row删除的
if self.refundType == .part && needAddGoods() { /// 保证不会发生数组越界的情况
if self.refundChooseGoods.count >= (index.section + 1) { self.refundChooseGoods.remove(at: index.section)
}
tableView.deleteSections([index.section], with: UITableView.RowAnimation.left)
self.resetIndexPath(false)
}else{ /// 保证不会发生数组越界的情况
if self.refundChooseGoods.count >= (index.row + 1) { self.refundChooseGoods.remove(at: index.row)
}
///print("-----------count ",self.refundChooseGoods.count)
///print("-----------",index.section,"--------",index.row)
tableView.deleteRows(at: [index], with: UITableView.RowAnimation.left)
self.resetIndexPath(true)
}
} /// 重新排列剩下的数据源的index,否则 crash
/// - Parameter updateRow: updateRow description
func resetIndexPath(_ updateRow:Bool) { var index = 0
for model in self.refundChooseGoods { if updateRow {
model.indexPath?.row = index
}else{
model.indexPath?.section = index
}
index += 1
}
}
最后看看我这样做之后删除组和删除cel分别的效果:

deleteSections & deleteRows 我踩得坑的更多相关文章
- 从零开始学 Java - Spring 支持 CORS 请求踩的坑
谁没掉进过几个大坑 记得好久之前,总能时不时在某个地方看到一些标语,往往都是上面一个伟人的头像,然后不管是不是他说的话,下面总是有看起来很政治正确且没卵用的屁话,我活到目前为止,最令我笑的肚子痛得是下 ...
- webuploader插件,我踩得坑
我在目前的公司做的项目要么是原生写法去做项目,要么就是vue+webpack做项目,但是vue这部分只是用了模板template,vue其他的都没用. 有一个项目需要做上传图片的功能,老大扔给我一个插 ...
- 谈谈调用腾讯云【OCR-通用印刷体识别】Api踩的坑
一.写在前面 最近做项目需要用到识别图片中文字的功能,本来用的Tesseract这个写的,不过效果不是很理想. 随后上网搜了一下OCR接口,就准备使用腾讯云.百度的OCR接口试一下效果.不过这个腾讯云 ...
- Asp.Net Core中使用Swagger,你不得不踩的坑
很久不来写blog了,换了新工作后很累,很忙.每天常态化加班到21点,偶尔还会到凌晨,加班很累,但这段时间,也确实学到了不少知识,今天这篇文章和大家分享一下:Asp.Net Core中使用Swagge ...
- python绘图踩的坑
踩的坑 pyecharts安装地图包 pip install echarts-countries-pypkg 报错Unknown or unsupported command 'install' 这可 ...
- 使用CCNode作为容器容易踩的坑
Cocos2dx中CCNode经常作为一个父容器,里面装一些UI控件,最后组成一个复杂的自定义的UI控件,但是在使用别人的自定义控件和自己写自定义问题的时候会踩一些坑. 首先拿到一个自定义的UI控件一 ...
- java基础不牢固容易踩的坑
java基础不牢固容易踩的坑 经过一年java后端代码以及对jdk源码阅读之后的总结,对java中一些基础中的容易忽略的东西写下来,给偏爱技术热爱开源的Coder们分享一下,避免在写代码中误入雷区. ...
- Ubuntu中安装FTP 服务器自己踩得坑
12点多了,擦!做个码农真不容易呀! 系统:Ubuntu16.04 安装:FTP 步骤: 1.不管有没有一上来我先卸载: sudo apt-get purge vsftpd 2.再安装:sudo ap ...
- python——pyinstaller踩的坑 UnicodeDecodeError
程序本身运行没任何毛病,奈何用pyinstaller -w xx.py的时候提示——UnicodeDecodeError: 'ascii' codec can't decode byte 0xb3 i ...
随机推荐
- 性能测试——记XX银行电票系统上线后宕机问题诊断优化
四月份我们公司负责的电票系统上线了,这个系统上线比客户方其他系统上线还特殊,是二期改造项目,旧系统数据还要整合抽取到新系统中继续使用,而且该系统不是增量型方式开发上线的,而且全部开发完后全国上线的,这 ...
- python3 之 迭代器与生成器
迭代器 迭代是Python最强大的功能之一,是访问集合元素的一种方式. 迭代器是一个可以记住遍历的位置的对象. 迭代器对象从集合的第一个元素开始访问,知道所有的元素被访问完结束. 迭代器只能往前不会后 ...
- Spring中常见的设计模式——单例模式
一.单例模式的应用场景 单例模式(singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点.J2EE中的ServletContext,ServletCon ...
- ES6扩展运算符...
对象的扩展运算符理解对象的扩展运算符其实很简单,只要记住一句话就可以: 对象中的扩展运算符(...)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中 let bar = { a: 1, b: 2 ...
- 树莓派SSH篇
开启好树莓派后发现一个问题,怎么才可以输入进树莓派里面呢? 一.你需要和我一样准备一个无线(有线)键盘
- 【Luogu P1168】【Luogu P1801&UVA 501】中位数&黑匣子(Black Box)——对顶堆相关
Luogu P1168 Luogu P1801 UVA 501(洛谷Remote Judge) 前置知识:堆.优先队列STL的使用 对顶堆 是一种在线维护第\(k\)小的算法. 其实就是开两个堆,一个 ...
- 天了噜,为什么外链css要放在头部,js要放在尾部?
我们最开始学前端的时候都会看到教程在处理外部css,js的时候会将css放在header中,js放在body的最后.为什么要这样子处理,今天参考一些资料好好分析下. 为什么外链css为什么要放头部? ...
- 记一次 Java 项目 CPU 占用久高不下故障处理
事件背景 公司对接了新系统,代码变动很大,项目也很急,于是在上线之后 Zabbix 不时就告警,提示 CPU 使用过载,告警消息类似如下: 一开始以为是系统停机升级,所有人都等着使用系统,导致系统处理 ...
- c# 窗体开发4 数据库访问技术
ADO.NET的名称起源于ADO(ACTIVEX DATA OBJECTS) USING SYSTEM; USING SYSTEM.COLLECTIONS.GENERIC; USING SYSTEM. ...
- 【SSL1457】翻币问题
题面: \[\Large\text{翻币问题}\] \[Time~Limit:1000MS~~Memory~Limit:65536K\] Description 有N个硬币(6<=N<=2 ...