崩溃:尝试将索引路径移动到不存在的索引路径
Crash: Attempt to move index path to index path that does not exist
有人在 here 之前问过这个错误,但他没有更新他的模型,据我所知,这就是我所做的一切,然后刷新 tableView,所以我想看看是否有人对此有任何想法.我真的很感激任何建议,因为我似乎无法深入了解这个问题,而且用户一直在报告这个问题。
问题是:
如果用户将任务从 Overdue 拖到任何其他部分,应用程序就会崩溃。错误是这样的:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to move index
path (<NSIndexPath: 0x9ed3b3d9edf53a85> {length = 2, path = 0 - 0}) to
index path (<NSIndexPath: 0x9ed3b3d9edf52a85> {length = 2, path = 1 -
0}) that does not exist - there are only 0 rows in section 1 after the
update'
来自分析的其他报告也通过类似(同样模糊的)堆栈跟踪指向此错误:
Invalid update: invalid number of rows in section 0. The number of
rows contained in an existing section after the update (1) must be
equal to the number of rows contained in that section before the
update (1), plus or minus the number of rows inserted or deleted from
that section (0 inserted, 0 deleted) and plus or minus the number of
rows moved into or out of that section (0 moved in, 1 moved out)
这是我的设置:
我有一个显示多个任务并按截止日期对它们进行分组的表格视图。每个任务的过滤数组都有一个变量,像这样:
class ZoneController: UIViewController {
var incompleteTasks: [Task] {
let tasks = zone.tasks.filter({ ![=10=].completed })
if zone.groupTasks { return tasks.sortedByDueDate() }
else { return tasks }
}
var overdueTasks: [Task] {
let tasks = zone.tasks.filter({ ([=10=].dueDate?.isInThePast ?? false) && ![=10=].completed })
if zone.groupTasks { return tasks.sortedByDueDate() }
else { return tasks }
}
var todayTasks: [Task] {
let tasks = zone.tasks.filter({ ([=10=].dueDate?.isToday ?? false) && ![=10=].completed && !([=10=].dueDate?.isInThePast ?? false) /* This is here because a task could be today but a few hours earlier, in which case it needs to be overdue */ })
if zone.groupTasks { return tasks.sortedByDueDate() }
else { return tasks }
}
var tomorrowTasks: [Task] {
let tasks = zone.tasks.filter({ ([=10=].dueDate?.isTomorrow ?? false) && ![=10=].completed })
if zone.groupTasks { return tasks.sortedByDueDate() }
else { return tasks }
}
var laterTasks: [Task] {
let laterTasks = zone.tasks.filter({
var isLater = true
if let dueDate = [=10=].dueDate { isLater = (dueDate > Calendar.current.dayAfterTomorrow()) }
return ![=10=].completed && isLater
})
return laterTasks.sortedByDueDate()
}
}
tableview 可以选择在组之间移动任务并更改其截止日期,因此在 tableView moveRowAt 中,我正在切换 destinationIndexPath 并相应地更改 dueDate,然后重新加载 tableView。这是负责 tableView 的代码:
import UIKit
import WidgetKit
import AppCenterAnalytics
extension ZoneController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tasksTabSelected {
if zone.groupTasks {
switch section {
case 0: return overdueTasks.count
case 1: return todayTasks.count
case 2: return tomorrowTasks.count
default: return laterTasks.count
}
} else {
return incompleteTasks.count
}
} else {
return zone.ideas.count
}
}
func numberOfSections(in tableView: UITableView) -> Int {
tasksTabSelected && zone.groupTasks ? 4 : 1
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if tasksTabSelected && zone.groupTasks && !incompleteTasks.isEmpty {
// This makes the headers scroll with the rest of the content
let dummyViewHeight = CGFloat(44)
tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.bounds.size.width, height: dummyViewHeight))
tableView.contentInset = UIEdgeInsets(top: -dummyViewHeight, left: 0, bottom: 0, right: 0)
let headerTitles = ["Overdue", "Today", "Tomorrow", "Later"]
let header = tableView.dequeueReusableCell(withIdentifier: "TaskHeaderCell") as! TaskHeaderCell
header.icon.image = UIImage(named: "header-\(headerTitles[section])")
header.name.text = headerTitles[section]
return header
} else {
return nil
}
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let footer = UIView()
footer.backgroundColor = .clear
return footer
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
var shouldBeTall = false
if tasksTabSelected && zone.groupTasks {
switch section {
case 0: shouldBeTall = overdueTasks.count != 0
case 1: shouldBeTall = todayTasks.count != 0
case 2: shouldBeTall = tomorrowTasks.count != 0
case 3: shouldBeTall = laterTasks.count != 0
default: return 0
}
}
return shouldBeTall ? 24 : 0
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
let tasksAreGrouped = tasksTabSelected && zone.groupTasks && !incompleteTasks.isEmpty
return tasksAreGrouped && section != 0 || tasksAreGrouped && section == 0 && !overdueTasks.isEmpty ? 48 : 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tasksTabSelected {
let cell = tableView.dequeueReusableCell(withIdentifier: "TaskCell") as! TaskCell
let accentColor = UIColor(hex: zone.firstColor)
var task: Task?
if zone.groupTasks {
print("Section: \(indexPath.section) has \(tableView.numberOfRows(inSection: indexPath.section))")
switch indexPath.section {
case 0: task = overdueTasks[indexPath.row]
case 1: task = todayTasks[indexPath.row]
case 2: task = tomorrowTasks[indexPath.row]
case 3: task = laterTasks[indexPath.row]
default: break
}
} else {
task = incompleteTasks[indexPath.row]
}
cell.task = task
cell.accentColor = accentColor
cell.zoneGroupsTasks = zone.groupTasks
cell.configure()
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "IdeaCell") as! IdeaCell
let idea = zone.ideas[indexPath.row]
cell.content.text = idea.content.replacingOccurrences(of: "\n\n\n\n", with: " ").replacingOccurrences(of: "\n\n\n", with: " ").replacingOccurrences(of: "\n\n", with: " ").replacingOccurrences(of: "\n", with: " ")
cell.idea = idea
return cell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tasksTabSelected {
let vc = Storyboards.main.instantiateViewController(identifier: "TaskDetails") as! TaskDetails
vc.colors = [UIColor(hex: zone.firstColor), UIColor(hex: zone.secondColor)]
if zone.groupTasks {
switch indexPath.section {
case 0: vc.task = overdueTasks[indexPath.row]
case 1: vc.task = todayTasks[indexPath.row]
case 2: vc.task = tomorrowTasks[indexPath.row]
case 3: vc.task = laterTasks[indexPath.row]
default: break
}
} else {
vc.task = incompleteTasks[indexPath.row]
}
presentSheet(vc)
} else {
let vc = Storyboards.main.instantiateViewController(identifier: "IdeaDetails") as! IdeaDetails
vc.color = UIColor(hex: zone.firstColor)
vc.idea = zone.ideas[indexPath.row]
presentSheet(vc)
}
}
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
if tasksTabSelected {
if zone.groupTasks && dragSourceIndexPath?.section != destinationIndexPath.section {
guard let dragSourceTimestamp = dragSourceTimestamp else { return }
var task = zone.tasks.first(where: { [=11=].id == dragSourceTimestamp })
task?.reminderNeeded = true
switch destinationIndexPath.section {
case 1:
task?.dueDate = Calendar.current.date(byAdding: .hour, value: 1, to: Date())
task?.reminderNeeded = false
case 2: task?.dueDate = Calendar.current.tomorrow(at: 9)
case 3: task?.dueDate = Calendar.current.inTwoWeeks(at: 9)
default: break
}
task?.save()
} else {
guard
let sourceIndex = zone.tasks.firstIndex(where: { [=11=].id == incompleteTasks[sourceIndexPath.row].id }),
let destinationIndex = zone.tasks.firstIndex(where: { [=11=].id == incompleteTasks[destinationIndexPath.row].id })
else { return }
Storage.zones[zoneIndex].tasks.move(from: sourceIndex, to: destinationIndex)
}
} else {
Storage.zones[zoneIndex].ideas.move(from: sourceIndexPath.row, to: destinationIndexPath.row)
}
reloadTableView() // This version is being called to make sure the zone is being refreshed, even though technically tableView.reloadData() would have been enough.
Push.updateAllReminders()
WidgetCenter.shared.reloadAllTimelines()
Analytics.trackEvent("Reordered tasks or ideas")
}
func tableView(_ tableView: UITableView, dragSessionWillBegin session: UIDragSession) {
tableView.vibrate()
// This is here to prevent users from dragging this task to other zones and a weird scrolling bug that happens
if let pageController = self.parent as? PVC { pageController.decouple() }
}
func tableView(_ tableView: UITableView, dragSessionDidEnd session: UIDragSession) {
// This is here to prevent users from dragging this task to other zones and a weird scrolling bug that happens
if let pageController = self.parent as? PVC { pageController.recouple() }
}
}
extension ZoneController: UITableViewDragDelegate {
func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
dragSourceIndexPath = indexPath
dragSourceTimestamp = (tableView.cellForRow(at: indexPath) as? TaskCell)?.task.id
return [UIDragItem(itemProvider: NSItemProvider())]
}
func tableView(_ tableView: UITableView, dragPreviewParametersForRowAt indexPath: IndexPath) -> UIDragPreviewParameters? {
let param = UIDragPreviewParameters()
if #available(iOS 14.0, *) { param.shadowPath = UIBezierPath(rect: .zero) }
param.backgroundColor = .clear
return param
}
}
extension ZoneController: UITableViewDropDelegate {
func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal {
if session.localDragSession != nil { // Drag originated from the same app.
let isSameSection = destinationIndexPath?.section == dragSourceIndexPath?.section
let permitted = !isSameSection && destinationIndexPath?.section != 0 || !tasksTabSelected || !zone.groupTasks
return UITableViewDropProposal(operation: permitted ? .move : .forbidden, intent: .insertAtDestinationIndexPath)
}
return UITableViewDropProposal(operation: .cancel, intent: .unspecified)
}
func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
}
func tableView(_ tableView: UITableView, dropPreviewParametersForRowAt indexPath: IndexPath) -> UIDragPreviewParameters? {
let param = UIDragPreviewParameters()
if #available(iOS 14.0, *) { param.shadowPath = UIBezierPath(rect: .zero) }
param.backgroundColor = .clear
return param
}
}
注意几点:
task.save() 方法将任务保存到磁盘。
如果我完全注释掉 moveRowAt 中的代码,崩溃仍然会发生,所以不是那里的东西引起的。
reloadTableView 方法在需要时设置一个空图像,并再次从磁盘读取区域(其中包含所有任务)。出于性能原因,我将区域存储在内存中作为 ViewController 的变量(如果我总是从磁盘读取它,滚动会非常卡顿)。
@objc func reloadTableView() {
DispatchQueue.main.async { [self] in
zone = Storage.zones[zoneIndex]
tableView.reloadData()
let hidden = tasksTabSelected && incompleteTasks.isEmpty || !tasksTabSelected && zone.ideas.isEmpty
emptyImage.isHidden = !hidden
emptyImage.image = UIImage(named: tasksTabSelected ? "no-tasks" : "no-ideas")
}
}
您需要更改 table 视图从中获取数据以响应 UI 更改的模型对象,然后重新加载 table。
目前,您似乎正在更改 Storage.zones
,但随后您将更新异步排队到用于呈现 table 视图的数据模型的视图 zone
副本。它们在一段时间内不同步(至少运行循环的一个周期),这很可能是您崩溃的时候。
您说您正在更新磁盘然后将其读回,但我没有看到读回它的代码,也没有看到明显调用该代码的代码。因此,尚不清楚这对设备负载等有多敏感,因此您遇到导致问题的延迟的可能性有多大。
旁白:如果您有多个项目,使用 UserDefaults 来存储它可能并不理想。
除非项目列表真的很小,否则在 numberOfRowsInSection
期间调用过滤器和排序不太理想(它经常被调用)。只要您正在缓存区域,您也可以将组织的数据缓存到您的过滤器返回的子列表中?
无论如何,这可能不是您崩溃的根本原因。
有人在 here 之前问过这个错误,但他没有更新他的模型,据我所知,这就是我所做的一切,然后刷新 tableView,所以我想看看是否有人对此有任何想法.我真的很感激任何建议,因为我似乎无法深入了解这个问题,而且用户一直在报告这个问题。
问题是: 如果用户将任务从 Overdue 拖到任何其他部分,应用程序就会崩溃。错误是这样的:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to move index path (<NSIndexPath: 0x9ed3b3d9edf53a85> {length = 2, path = 0 - 0}) to index path (<NSIndexPath: 0x9ed3b3d9edf52a85> {length = 2, path = 1 - 0}) that does not exist - there are only 0 rows in section 1 after the update'
来自分析的其他报告也通过类似(同样模糊的)堆栈跟踪指向此错误:
Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 1 moved out)
这是我的设置: 我有一个显示多个任务并按截止日期对它们进行分组的表格视图。每个任务的过滤数组都有一个变量,像这样:
class ZoneController: UIViewController {
var incompleteTasks: [Task] {
let tasks = zone.tasks.filter({ ![=10=].completed })
if zone.groupTasks { return tasks.sortedByDueDate() }
else { return tasks }
}
var overdueTasks: [Task] {
let tasks = zone.tasks.filter({ ([=10=].dueDate?.isInThePast ?? false) && ![=10=].completed })
if zone.groupTasks { return tasks.sortedByDueDate() }
else { return tasks }
}
var todayTasks: [Task] {
let tasks = zone.tasks.filter({ ([=10=].dueDate?.isToday ?? false) && ![=10=].completed && !([=10=].dueDate?.isInThePast ?? false) /* This is here because a task could be today but a few hours earlier, in which case it needs to be overdue */ })
if zone.groupTasks { return tasks.sortedByDueDate() }
else { return tasks }
}
var tomorrowTasks: [Task] {
let tasks = zone.tasks.filter({ ([=10=].dueDate?.isTomorrow ?? false) && ![=10=].completed })
if zone.groupTasks { return tasks.sortedByDueDate() }
else { return tasks }
}
var laterTasks: [Task] {
let laterTasks = zone.tasks.filter({
var isLater = true
if let dueDate = [=10=].dueDate { isLater = (dueDate > Calendar.current.dayAfterTomorrow()) }
return ![=10=].completed && isLater
})
return laterTasks.sortedByDueDate()
}
}
tableview 可以选择在组之间移动任务并更改其截止日期,因此在 tableView moveRowAt 中,我正在切换 destinationIndexPath 并相应地更改 dueDate,然后重新加载 tableView。这是负责 tableView 的代码:
import UIKit
import WidgetKit
import AppCenterAnalytics
extension ZoneController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tasksTabSelected {
if zone.groupTasks {
switch section {
case 0: return overdueTasks.count
case 1: return todayTasks.count
case 2: return tomorrowTasks.count
default: return laterTasks.count
}
} else {
return incompleteTasks.count
}
} else {
return zone.ideas.count
}
}
func numberOfSections(in tableView: UITableView) -> Int {
tasksTabSelected && zone.groupTasks ? 4 : 1
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if tasksTabSelected && zone.groupTasks && !incompleteTasks.isEmpty {
// This makes the headers scroll with the rest of the content
let dummyViewHeight = CGFloat(44)
tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.bounds.size.width, height: dummyViewHeight))
tableView.contentInset = UIEdgeInsets(top: -dummyViewHeight, left: 0, bottom: 0, right: 0)
let headerTitles = ["Overdue", "Today", "Tomorrow", "Later"]
let header = tableView.dequeueReusableCell(withIdentifier: "TaskHeaderCell") as! TaskHeaderCell
header.icon.image = UIImage(named: "header-\(headerTitles[section])")
header.name.text = headerTitles[section]
return header
} else {
return nil
}
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let footer = UIView()
footer.backgroundColor = .clear
return footer
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
var shouldBeTall = false
if tasksTabSelected && zone.groupTasks {
switch section {
case 0: shouldBeTall = overdueTasks.count != 0
case 1: shouldBeTall = todayTasks.count != 0
case 2: shouldBeTall = tomorrowTasks.count != 0
case 3: shouldBeTall = laterTasks.count != 0
default: return 0
}
}
return shouldBeTall ? 24 : 0
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
let tasksAreGrouped = tasksTabSelected && zone.groupTasks && !incompleteTasks.isEmpty
return tasksAreGrouped && section != 0 || tasksAreGrouped && section == 0 && !overdueTasks.isEmpty ? 48 : 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if tasksTabSelected {
let cell = tableView.dequeueReusableCell(withIdentifier: "TaskCell") as! TaskCell
let accentColor = UIColor(hex: zone.firstColor)
var task: Task?
if zone.groupTasks {
print("Section: \(indexPath.section) has \(tableView.numberOfRows(inSection: indexPath.section))")
switch indexPath.section {
case 0: task = overdueTasks[indexPath.row]
case 1: task = todayTasks[indexPath.row]
case 2: task = tomorrowTasks[indexPath.row]
case 3: task = laterTasks[indexPath.row]
default: break
}
} else {
task = incompleteTasks[indexPath.row]
}
cell.task = task
cell.accentColor = accentColor
cell.zoneGroupsTasks = zone.groupTasks
cell.configure()
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "IdeaCell") as! IdeaCell
let idea = zone.ideas[indexPath.row]
cell.content.text = idea.content.replacingOccurrences(of: "\n\n\n\n", with: " ").replacingOccurrences(of: "\n\n\n", with: " ").replacingOccurrences(of: "\n\n", with: " ").replacingOccurrences(of: "\n", with: " ")
cell.idea = idea
return cell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tasksTabSelected {
let vc = Storyboards.main.instantiateViewController(identifier: "TaskDetails") as! TaskDetails
vc.colors = [UIColor(hex: zone.firstColor), UIColor(hex: zone.secondColor)]
if zone.groupTasks {
switch indexPath.section {
case 0: vc.task = overdueTasks[indexPath.row]
case 1: vc.task = todayTasks[indexPath.row]
case 2: vc.task = tomorrowTasks[indexPath.row]
case 3: vc.task = laterTasks[indexPath.row]
default: break
}
} else {
vc.task = incompleteTasks[indexPath.row]
}
presentSheet(vc)
} else {
let vc = Storyboards.main.instantiateViewController(identifier: "IdeaDetails") as! IdeaDetails
vc.color = UIColor(hex: zone.firstColor)
vc.idea = zone.ideas[indexPath.row]
presentSheet(vc)
}
}
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
if tasksTabSelected {
if zone.groupTasks && dragSourceIndexPath?.section != destinationIndexPath.section {
guard let dragSourceTimestamp = dragSourceTimestamp else { return }
var task = zone.tasks.first(where: { [=11=].id == dragSourceTimestamp })
task?.reminderNeeded = true
switch destinationIndexPath.section {
case 1:
task?.dueDate = Calendar.current.date(byAdding: .hour, value: 1, to: Date())
task?.reminderNeeded = false
case 2: task?.dueDate = Calendar.current.tomorrow(at: 9)
case 3: task?.dueDate = Calendar.current.inTwoWeeks(at: 9)
default: break
}
task?.save()
} else {
guard
let sourceIndex = zone.tasks.firstIndex(where: { [=11=].id == incompleteTasks[sourceIndexPath.row].id }),
let destinationIndex = zone.tasks.firstIndex(where: { [=11=].id == incompleteTasks[destinationIndexPath.row].id })
else { return }
Storage.zones[zoneIndex].tasks.move(from: sourceIndex, to: destinationIndex)
}
} else {
Storage.zones[zoneIndex].ideas.move(from: sourceIndexPath.row, to: destinationIndexPath.row)
}
reloadTableView() // This version is being called to make sure the zone is being refreshed, even though technically tableView.reloadData() would have been enough.
Push.updateAllReminders()
WidgetCenter.shared.reloadAllTimelines()
Analytics.trackEvent("Reordered tasks or ideas")
}
func tableView(_ tableView: UITableView, dragSessionWillBegin session: UIDragSession) {
tableView.vibrate()
// This is here to prevent users from dragging this task to other zones and a weird scrolling bug that happens
if let pageController = self.parent as? PVC { pageController.decouple() }
}
func tableView(_ tableView: UITableView, dragSessionDidEnd session: UIDragSession) {
// This is here to prevent users from dragging this task to other zones and a weird scrolling bug that happens
if let pageController = self.parent as? PVC { pageController.recouple() }
}
}
extension ZoneController: UITableViewDragDelegate {
func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
dragSourceIndexPath = indexPath
dragSourceTimestamp = (tableView.cellForRow(at: indexPath) as? TaskCell)?.task.id
return [UIDragItem(itemProvider: NSItemProvider())]
}
func tableView(_ tableView: UITableView, dragPreviewParametersForRowAt indexPath: IndexPath) -> UIDragPreviewParameters? {
let param = UIDragPreviewParameters()
if #available(iOS 14.0, *) { param.shadowPath = UIBezierPath(rect: .zero) }
param.backgroundColor = .clear
return param
}
}
extension ZoneController: UITableViewDropDelegate {
func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal {
if session.localDragSession != nil { // Drag originated from the same app.
let isSameSection = destinationIndexPath?.section == dragSourceIndexPath?.section
let permitted = !isSameSection && destinationIndexPath?.section != 0 || !tasksTabSelected || !zone.groupTasks
return UITableViewDropProposal(operation: permitted ? .move : .forbidden, intent: .insertAtDestinationIndexPath)
}
return UITableViewDropProposal(operation: .cancel, intent: .unspecified)
}
func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
}
func tableView(_ tableView: UITableView, dropPreviewParametersForRowAt indexPath: IndexPath) -> UIDragPreviewParameters? {
let param = UIDragPreviewParameters()
if #available(iOS 14.0, *) { param.shadowPath = UIBezierPath(rect: .zero) }
param.backgroundColor = .clear
return param
}
}
注意几点:
task.save() 方法将任务保存到磁盘。
如果我完全注释掉 moveRowAt 中的代码,崩溃仍然会发生,所以不是那里的东西引起的。
reloadTableView 方法在需要时设置一个空图像,并再次从磁盘读取区域(其中包含所有任务)。出于性能原因,我将区域存储在内存中作为 ViewController 的变量(如果我总是从磁盘读取它,滚动会非常卡顿)。
@objc func reloadTableView() { DispatchQueue.main.async { [self] in zone = Storage.zones[zoneIndex] tableView.reloadData() let hidden = tasksTabSelected && incompleteTasks.isEmpty || !tasksTabSelected && zone.ideas.isEmpty emptyImage.isHidden = !hidden emptyImage.image = UIImage(named: tasksTabSelected ? "no-tasks" : "no-ideas") } }
您需要更改 table 视图从中获取数据以响应 UI 更改的模型对象,然后重新加载 table。
目前,您似乎正在更改 Storage.zones
,但随后您将更新异步排队到用于呈现 table 视图的数据模型的视图 zone
副本。它们在一段时间内不同步(至少运行循环的一个周期),这很可能是您崩溃的时候。
您说您正在更新磁盘然后将其读回,但我没有看到读回它的代码,也没有看到明显调用该代码的代码。因此,尚不清楚这对设备负载等有多敏感,因此您遇到导致问题的延迟的可能性有多大。
旁白:如果您有多个项目,使用 UserDefaults 来存储它可能并不理想。
除非项目列表真的很小,否则在 numberOfRowsInSection
期间调用过滤器和排序不太理想(它经常被调用)。只要您正在缓存区域,您也可以将组织的数据缓存到您的过滤器返回的子列表中?
无论如何,这可能不是您崩溃的根本原因。