You certainly got this error:
CoreData: error: Serious application error. An exception was caught
from the delegate of NSFetchedResultsController during a call to
-controllerDidChangeContent:. 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 (1 inserted, 0 deleted). with userInfo
(null)
This message just doesn't make sense to me as CoreData is related to NSFetchedResultsController BUT CoreData is not related to the table view. (Correct me if I'm wrong)
The way I understand it, the only link between tableview and NSFetchedResultsController is in NSFetchedResultsController's delegate, when we use controller(didChangeObject:) and controller(didChangeSection:)
How is it possible that the iOS framework knows about the tableView number of sections when it's not even mandatory (It's recommended but not mandatory) to use a UITableView...
It should only be able to check the NSFetchedResultsController result fetchedObjects result array and not in the tableview.
Typically, the controller's delegate is implemented in a following way (taken from Apple's Core Data Programming Guide):
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[[self tableView] endUpdates];
}
When the tableView gets endUpdates message, it does two things that are important in our context:
Counts how many sections were inserted and / or deleted since beginUpdates (by using insertSections:withRowAnimation method)
Checks if these changes were reflected in the data source. In your case, they were not: there was 1 section inserted by calling insertSections:withRowAnimation on the table view, but the data source still returns 8 when asked about section count. That's basically what the error message is saying.
So the table view figures out that its state is inconsistent and it throws and exception.
And we're still in the controllerDidChangeContent delegate method. Let's take a look on the error message again:
An exception was caught from the delegate of
NSFetchedResultsController during a call to
-controllerDidChangeContent
So NSFetchedResultsController calls controllerDidChangeContent on its delegate inside a try block and catches the exception. Then it re-throws it.
Long story short: NSFetchedResultsController knows nothing about the table view - it just caught the exception which happened to be thrown in endUpdates method in UITableView.
Related
I have found a fix for this, but I'm not really liking the fix. My issue goes like this. I am using a NSFetchedResultsController to populate a UICollectionView-- which displays a collection of images. Each image is described by a Core Data object (e.g., its file name is in the Core Data object).
I have UI controls that allow a user to delete multiple images at the same time, and was having a problem when the user would delete more than one object. The code to do the deletion was:
for image in images {
CoreData.sessionNamed(CoreDataExtras.sessionName).remove(image)
}
CoreData.sessionNamed(CoreDataExtras.sessionName).saveContext()
(Some of this is my library code).
With the deletion of two objects, I get a crash and the following log message:
CoreData: error: Serious application error. Exception was caught
during Core Data change processing. This is usually a bug within an
observer of NSManagedObjectContextObjectsDidChangeNotification.
Invalid update: invalid number of items in section 0. The number of
items contained in an existing section after the update (99) must be
equal to the number of items contained in that section before the
update (101), plus or minus the number of items inserted or deleted
from that section (0 inserted, 1 deleted) and plus or minus the number
of items moved into or out of that section (0 moved in, 0 moved out).
with userInfo (null)
What fixes the problem is if I change the deletion code to:
for image in images {
CoreData.sessionNamed(CoreDataExtras.sessionName).remove(image)
CoreData.sessionNamed(CoreDataExtras.sessionName).saveContext()
}
I guess the problem is that in the delegate callback method:
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
I do:
collectionView.deleteItems(at: [indexPath])
Apparently, you can either do a reloadItems in the didChangeObject method, or you can do a saveContext after each object deletion.
If you delete several images, and then save the context, the FRC processes all the deletions - so its sections, fetchedObjects, etc, reflect all those changes. But it then calls the didChangeObject: delegate method separately for each change. In that method, you call the collectionView update methods (eg. deleteItems); the collectionView then calls its dataSource methods and does a quick tally up: there were X items, Y items were deleted, there are now Z items and throws an error because Z != X-Y.
When a FRC is used with a tableView, this problem is overcome by using the tableView beginUpdates and endUpdates calls in the FRC controllerWillChangeContent: and controllerDidChangeContent: delegate methods. This causes the tableView to defer doing the tally up until ALL the individual changes have been processed - at which point the numbers do add up.
Your solution - to call saveContext after each deletion - causes the FRC to process each deletion in turn: updating its sections, fetchedObjects, etc, to reflect only one deletion at a time. This keeps the FRC's data in sync with the collectionView. One possible refinement would be to call processPendingChanges on the context after each deletion, instead of saving the context. This avoids saving data when you might not want to, but nonetheless causes each deletion to be processed separately.
The alternative is to mimic the tableView's beginUpdates/endUpdates mechanism for holding all the collectionView updates until all the FRC updates have been processed. This works broadly as follows:
Create arrays to keep track of the changes (inserts, deletes).
Each time didChangeObject: is called, add the corresponding indexPath to the relevant array.
When controllerDidChangeContent: is called, iterate through the arrays (deletions first, when inserts) calling the corresponding collectionView update methods. (Then empty the arrays ready for the next batch of updates).
Some good explanations and potential implementations are included in this question and its answers.
Has anyone been able to solve what causes this error to occur,
"CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Attempt to create two animations for cell with userInfo (null)"
Seems like its more of a tableview issue then core data
It has been some time since I worked with core data and the table view updates, but I'd suggest to look for multiple cell updates ending up at one cell.
For instance if two records have been deleted, the change should propagate to the table view. If these two deletions are propagated as "remove cell 7" (two times) while it should be cell 7 and 8, it might explain the error. Maybe an index value is calculated and after the first deletion that index needs to be adjusted or recalculated, but it is not and thus points to the wrong cell for the next deletion.
My tableview is getting reloaded and gets new data for every 10secs by running timer in background thread.But when data is getting reloaded and the same time when I'm scrolling the tableview .
App is getting crashed.leaving the error as
[__NSArrayM objectAtIndex:]: index 18 beyond bounds [0 .. 15].
You need to remove objects from your array when you get the response. I guess you are removing your array first and then your timer is executing.
You haven't provided any code, so this is a it of a guess, but as others have said it seems likely that you are reloading your data directly into the array that is your table data source. This is causing a timing related issue where your data source array and the information you have provided to the table view previously (specifically number of rows) is inconsistent.
You should fetch your new data into a different array and then once all data has been fetched you can update the reference to your table data source array and reload the table view.
Another approach is to compare the two arrays to determine which rows have been deleted and which rows are new. You can then call delete/insert rows as appropriate. Make sure you call beginUpdates before you start adding/deleting rows and endUpdates once you have finished.
Please try to reload your table in main thread. Like below code
dispatch_async(dispatch_get_main_queue(), ^{
[self.collection reloadData];
});
I have a UITableView on one screen where the underlying datasource may change in structure - not just the individual cells.
If I jsut do that on the same UITableView and then call reload I get an error that the structure of the table has changed (like different number of rows, sections etc)
This is the error I get:
* Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'Invalid update: invalid
number of sections. The number of sections contained in the table
view after the update (1) must be equal to the number of sections
contained in the table view before the update (21), plus or minus the
number of sections inserted or deleted (0 inserted, 0 deleted).'
Do I really have to change my table by deleting all sections and then adding the new ones?
So basically my question is how to do this correctly.
I could just create a new UITableView programmatically, use the rect, bkgnd color, delegate and datasource from the initial table - but all the constraints I set in IB are lost for the new table.
How can I take all these constraints from the old table and give them to the new one?
Even if I just set a new datasource I get the above error. Only if I
create a new UITableView with a new datasource I do not get the above
error.
Or is there another way to reset a UITableView with totally different underlying data while keeping all its UI attributes and constraints untouched?
All you need to do is assign new data to whatever variables the various data source methods reference and call reloadData on the table view.
In other words, at the time you call reloadData, the calls to numberOfSections, numberOfRowsInSection, and cellForRowAtIndexPath etc. must all be referencing the same updated data.
I have a UITableView that is backed by core data with sections grouped by date. I can delete and add while I'm on the that view just fine. The NSFetchController and the UITableView stay in sync just fine. But then I drill down to an individual object, make changes, save and return. I don't add from the other view, but sometimes delete, most of the the time I'm just making edits to existing records, and none of the edits would cause them to change which section they belonged to. At that point if I try to delete a row I get:
Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-1448.89/UITableView.m:974
2011-10-01 14:55:42.691 Lotus Bud[8073:fa03] Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of sections. The number of sections contained in the table view after the update (1) must be equal to the number of sections contained in the table view before the update (1), plus or minus the number of sections inserted or deleted (0 inserted, 1 deleted). with userInfo (null)
What do I need to do to get them back in sync after saving from another view.
I would suggest listening for the NSManagedObjectContextDidSaveNotification and breaking in the debugger when it is fired. Then you can see what section is being deleted and hopefully backtrace to the source of the problem.