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 ...
随机推荐
- Linux基础命令复习01
一.Linux中的基本查看.查找命令: 1.ls 查看目录信息: -l #查看属性,以长格式显示 -d #查看本身属性 -A #显示包括以.开头的隐藏文档 -h #提供易读的单位 -R #表示递 ...
- Android 获取 SHA1值3步完成
未经允许,禁止
- 2019-11-26:密码学基础知识,csrf防御
信息安全的基础是数学--->密码算法--->安全协议(ssl VPN)-->应用(证书 PKI)密码学入门密码编码学:研究加解密算法的学科密码分析学:研究破译密码算法的学科 加解密分 ...
- 宋宝华:关于ARM Linux原子操作的实现
本文系转载,著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 作者: 宋宝华 来源: 微信公众号linux阅码场(id: linuxdev) 竞态无所不在 首先我们要理解竞态(ra ...
- cesium定义线面
面: var polygon = viewer.entities.add({ polygon : { hierarchy : { positions : null, holes : [{ positi ...
- vue 结合 Echarts 实现半开环形图
Echarts 实现半开环形图 1.先看看实现的图 2.HTML部分 创建id 是 chart 的div标签. <div class="content-item"> & ...
- Ansible Playbooks 介绍 和 使用 一
目录 Ansible Playbooks Playbooks 组成部分: YAML 介绍 YAML 语法 Ansible 基础元素 变量 facts registre 通过命令传递变量 通过roles ...
- C语言博客作业11
一.本周教学内容&目标 第5章 函数 要求学生掌握各种类型函数的定义.调用和申明,熟悉变量的作用域.生存周期和存储类型. 二.本周作业头 这个作业属于那个课程 C语言程序设计II 这个作业要求 ...
- 神奇的 SQL 之 联表细节 → MySQL JOIN 的执行过程(一)
开心一刻 我:嗨,老板娘,有冰红茶没 老板娘:有 我:多少钱一瓶 老板娘:3块 我:给我来一瓶,给,3块 老板娘:来,你的冰红茶 我:玩呐,我要冰红茶,你给我个瓶盖干哈? 老板娘:这是再来一瓶,我家卖 ...
- 转:关于java.lang.ClassNotFoundException: org.springframework.boot.SpringApplication的解决
在这个控制板中,出现了这个问题 java.lang.ClassNotFoundException: org.springframework.boot.SpringApplication 虽然明显知道是 ...