无效更新:第 0 节中 NSFetchedResultsController 的行数无效
Invalid update: invalid number of rows in section 0 with NSFetchedResultsController
我有 2 个视图控制器:
VC1 根据 CoreData 属性 isPicked 填充其 tableView,该属性为 bool 并且仅显示具有真实状态的项目。 VC2 是第二个模态(非全屏)视图控制器,它允许用户更改 isPicked 属性的状态:选中和取消选中项目(使其为真或假)。这个想法是用户选择需要的项目结束关闭 VC2,并且这些项目应该出现在 VC1 中。
我已经为 VC1 和 VC2 实现了 NSFetchedResultsController
。一旦我按下第一项(即将 isPicked 状态更改为 true),我就会收到来自 VC1 的错误消息:
[error] fault: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) 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, 0 moved out).
以下是我在 VC2 (true\false) 中更改项目状态的方法:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let currencyArray = fetchedResultsController.fetchedObjects else { return }
let cell = tableView.cellForRow(at: indexPath) as! PickCurrencyTableViewCell
for currency in currencyArray {
if currency.shortName == cell.shortName.text {
currency.isPicked = !currency.isPicked
coreDataManager.save()
}
}
}
这是我的 VC1 NSFetchedResultsController 实现:
override func viewDidLoad() {
super.viewDidLoad()
setupFetchedResultsController()
}
func setupFetchedResultsController() {
let predicate = NSPredicate(format: "isForConverter == YES")
fetchedResultsController = createCurrencyFetchedResultsController(and: predicate)
fetchedResultsController.delegate = self
try? fetchedResultsController.performFetch()
}
func createCurrencyFetchedResultsController(with request: NSFetchRequest<Currency> = Currency.fetchRequest(), and predicate: NSPredicate? = nil, sortDescriptor: [NSSortDescriptor] = [NSSortDescriptor(key: "shortName", ascending: true)]) -> NSFetchedResultsController<Currency> {
request.predicate = predicate
request.sortDescriptors = sortDescriptor
return NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
}
这是我的 VC1 NSFetchedResultsController 委托:
extension VC1: NSFetchedResultsControllerDelegate {
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
if let indexPath = indexPath, let newIndexPath = newIndexPath {
switch type {
case .update:
tableView.reloadRows(at: [indexPath], with: .none)
case .move:
tableView.moveRow(at: indexPath, to: newIndexPath)
case .delete:
tableView.deleteRows(at: [indexPath], with: .none)
case .insert:
tableView.insertRows(at: [indexPath], with: .none)
default:
tableView.reloadData()
}
}
}
}
当我重新加载应用程序时,选择的项目会在 VC1 中自行显示(导致崩溃)。但是 VC2 中的每一次更改都会再次使应用程序崩溃并出现相同的错误。我没有任何方法可以删除 VC1 左右的项目。我需要 VC1 tableView 根据 VC2 制作的 isPicked 状态显示或隐藏项目。
我的代码中遗漏了什么?
更新:我的 VC1 TableView 方法
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let currency = fetchedResultsController.sections![section]
return currency.numberOfObjects
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "converterCell", for: indexPath) as! ConverterTableViewCell
let currency = fetchedResultsController.object(at: indexPath)
cell.shortName.text = currency.shortName
cell.fullName.text = currency.fullName
return cell
}
文档指出:
控制器(_:didChange:at:for:newIndexPath:)
indexPath
The index path of the changed object (this value is nil for
insertions).
因此,您应该像这样更改代码:
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .update:
if let indexPath = indexPath {
tableView.reloadRows(at: [indexPath], with: .none)
}
case .move:
if let indexPath = indexPath {
tableView.moveRow(at: indexPath, to: newIndexPath)
}
case .delete:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .none)
tableView.reloadData() // << May be needed ?
}
case .insert:
if let newIndexPath = newIndexPath {
tableView.insertRows(at: [newIndexPath], with: .none)
tableView.reloadData() // << May be needed ?
}
default:
tableView.reloadData()
}
}
}
我有 2 个视图控制器:
VC1 根据 CoreData 属性 isPicked 填充其 tableView,该属性为 bool 并且仅显示具有真实状态的项目。 VC2 是第二个模态(非全屏)视图控制器,它允许用户更改 isPicked 属性的状态:选中和取消选中项目(使其为真或假)。这个想法是用户选择需要的项目结束关闭 VC2,并且这些项目应该出现在 VC1 中。
我已经为 VC1 和 VC2 实现了 NSFetchedResultsController
。一旦我按下第一项(即将 isPicked 状态更改为 true),我就会收到来自 VC1 的错误消息:
[error] fault: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (2) 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, 0 moved out).
以下是我在 VC2 (true\false) 中更改项目状态的方法:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let currencyArray = fetchedResultsController.fetchedObjects else { return }
let cell = tableView.cellForRow(at: indexPath) as! PickCurrencyTableViewCell
for currency in currencyArray {
if currency.shortName == cell.shortName.text {
currency.isPicked = !currency.isPicked
coreDataManager.save()
}
}
}
这是我的 VC1 NSFetchedResultsController 实现:
override func viewDidLoad() {
super.viewDidLoad()
setupFetchedResultsController()
}
func setupFetchedResultsController() {
let predicate = NSPredicate(format: "isForConverter == YES")
fetchedResultsController = createCurrencyFetchedResultsController(and: predicate)
fetchedResultsController.delegate = self
try? fetchedResultsController.performFetch()
}
func createCurrencyFetchedResultsController(with request: NSFetchRequest<Currency> = Currency.fetchRequest(), and predicate: NSPredicate? = nil, sortDescriptor: [NSSortDescriptor] = [NSSortDescriptor(key: "shortName", ascending: true)]) -> NSFetchedResultsController<Currency> {
request.predicate = predicate
request.sortDescriptors = sortDescriptor
return NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
}
这是我的 VC1 NSFetchedResultsController 委托:
extension VC1: NSFetchedResultsControllerDelegate {
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
if let indexPath = indexPath, let newIndexPath = newIndexPath {
switch type {
case .update:
tableView.reloadRows(at: [indexPath], with: .none)
case .move:
tableView.moveRow(at: indexPath, to: newIndexPath)
case .delete:
tableView.deleteRows(at: [indexPath], with: .none)
case .insert:
tableView.insertRows(at: [indexPath], with: .none)
default:
tableView.reloadData()
}
}
}
}
当我重新加载应用程序时,选择的项目会在 VC1 中自行显示(导致崩溃)。但是 VC2 中的每一次更改都会再次使应用程序崩溃并出现相同的错误。我没有任何方法可以删除 VC1 左右的项目。我需要 VC1 tableView 根据 VC2 制作的 isPicked 状态显示或隐藏项目。
我的代码中遗漏了什么?
更新:我的 VC1 TableView 方法
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let currency = fetchedResultsController.sections![section]
return currency.numberOfObjects
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "converterCell", for: indexPath) as! ConverterTableViewCell
let currency = fetchedResultsController.object(at: indexPath)
cell.shortName.text = currency.shortName
cell.fullName.text = currency.fullName
return cell
}
文档指出:
控制器(_:didChange:at:for:newIndexPath:)
indexPath
The index path of the changed object (this value is nil for insertions).
因此,您应该像这样更改代码:
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .update:
if let indexPath = indexPath {
tableView.reloadRows(at: [indexPath], with: .none)
}
case .move:
if let indexPath = indexPath {
tableView.moveRow(at: indexPath, to: newIndexPath)
}
case .delete:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .none)
tableView.reloadData() // << May be needed ?
}
case .insert:
if let newIndexPath = newIndexPath {
tableView.insertRows(at: [newIndexPath], with: .none)
tableView.reloadData() // << May be needed ?
}
default:
tableView.reloadData()
}
}
}