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 ...
随机推荐
- markdown总结 (webstrom快捷键)
# 在HbuilderX中写markdown(WebStrom快捷键配置)0. 一些快捷键和鼠标操作:1. ctrl+shift+↑ 当前行或者选中的块整体向上移动 ↓同理2. 向两侧扩大选择:A ...
- docker中部署项目时遇到的问题
容器和宿主机时间不同步问题? 将本地时间复制到docker容器内的etc文件夹下即可 docker cp /etc/localtime scrapy_8:/etc/ 启动crontab错误? 报错: ...
- 推荐算法之用矩阵分解做协调过滤——LFM模型
隐语义模型(Latent factor model,以下简称LFM),是推荐系统领域上广泛使用的算法.它将矩阵分解应用于推荐算法推到了新的高度,在推荐算法历史上留下了光辉灿烂的一笔.本文将对 LFM ...
- JVM的内存回收机制
垃圾回收机制,简称gc.对堆与方法区的对象进行回收,因为java不像c需要编程人员手动clear,虚拟机通过垃圾回收算法,对堆与方法区的对象进行自动回收处理. 1.引用计数法(jvm没有采用,因为当两 ...
- scala学习系列二
一 scala语言开发注意事项: 1 Scala程序的执行入口是main()函数 2 Scala语言严格区分大小写. 3 Scala方法由一条条语句构成,每个语句后不需要分号(Scala语言会在每行后 ...
- tensorflow:模型的保存和训练过程可视化
在使用tf来训练模型的时候,难免会出现中断的情况.这时候自然就希望能够将辛辛苦苦得到的中间参数保留下来,不然下次又要重新开始. 保存模型的方法: #之前是各种构建模型graph的操作(矩阵相乘,sig ...
- mysql如何设置主从(读写分离),redis发布功能,以及redis的持久化存储(rdb,aof)
1 mysql基本命令 1.启动mysql systemctl start mariadb 2.linux客户端连接自己 mysql -uroot -p -h 127.0.0.1 3.远程链接mysq ...
- Spring Boot通过ImportBeanDefinitionRegistrar动态注入Bean
在阅读Spring Boot源码时,看到Spring Boot中大量使用ImportBeanDefinitionRegistrar来实现Bean的动态注入.它是Spring中一个强大的扩展接口.本篇文 ...
- 程序员的算法课(18)-常用的图算法:广度优先(BFS)
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/m0_37609579/article/de ...
- Ansible 介绍和安装
目录 Ansible 介绍 环境准备 Ansible安装 配置秘钥管理 配置Inventory文件 简单测试连通性 Ansible 介绍 运维工具分类: agent: puppet, func 这类都 ...