NSFetchedResultsController delegate exception - cocoa-touch

I have an app which uses a table view to display a list of items form my core data. I am using a remote api and I am updating my content after pulling down the table view - this triggers a call to the API.
Data is retrieved, parsed and inserted/updated into my Core Data.
I am sometimes getting an error after saving my Core Data context... Note that I'm not using multiple threads for this and like I said it doesn't seem to always happen.
I am literally going quite mad. It seems this guy has a similar issue but I'm still unable to fix mine with his solution:
CoreData error driving me crazy... CoreData: Serious application error. An exception caught from delegate of NSFetchedResultsController
Here is the full error:
2012-07-31 14:14:47.332 MyApp[2893:11303]
*** Assertion failure in -[_UITableViewUpdateSupport _setupAnimationsForNewlyInsertedCells],
/SourceCache/UIKit_Sim/UIKit-1914.84/UITableViewSupport.m:1133
2012-07-31 14:14:47.332 MyApp[2893:11303] 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)
UPDATE:
I have a predicate on my fetch request. In order to seem to be deleting objects previously downloaded from API and which are missing from the new JSON result. I am setting a hideFromUser flag, this is saved in my Core Data.
If this flag is YES then it doesn't appear in the table view. But if it's ok then it does. I am also updating info on that managed object should anything be changed. Is it possible that I have an object which was previously set to hide... and is now set to show, and it also had some new data, could this possible trigger a "cell should update" and a "cell should insert" ?
More I think about it less it seems to be relevant.
Here is how I am updating my data:
1) I set all relevant objects of the corresponding type to "hide form user" (NSPredicate ensures they don't show in Table View).
2) I get an NSArray from the JSON data.
3) Looping each item, my createABookOfClass:withJSON: method queries the core data for a book (using an ID from the json dictionary), if it doesn't find it, it creates a new one. Note: at this point the "hide from user flag" is reverted.
4) After all is done, I save.
[[DPLocalStore getInstance] hideFlagItemsOfType:NSStringFromClass([MyFavouriteBook class])];
NSArray * itemsJSON = [data mutableObjectFromJSONData];
[itemsJSON enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) {
[[DPLocalStore getInstance] createABookOfClass:[MyFavouriteBook class]
withJSON:obj];
}];
NSError *error = nil;
BOOL didsave = [[DPLocalStore getInstance] save:&error];
Maybe what is happening is a cell containing Object A has been updated, it's update: the hide flag has changed. thus I am getting into a situation where the NSFetchedResultsController's delegate wants to update that cell, and delete it also... since the predicate now doesn't correspond to this object... That sound very likely...

I think I may have found the error, since I changed it I haven't experienced the problem so I am assuming that it's ok.
In my implementation of controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: I had this in the switch statement for NSFetchedResultsChangeMove:
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
// Reloading the section inserts a new row and ensures that titles are updated appropriately.
[tableView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section]
withRowAnimation:UITableViewRowAnimationFade];
break;
As it was used in an older project (which I was not part of), I assumed the entire controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: implementation was okay, since this really is more of a template rather then something you will do anything customised.
In consequence I didn't pay much attention to it until I was reading up about it again on the Apple developer site. And I noticed, in case of a NSFetchedResultsChangeMove you must delete a cell, and insert the cell at the new path. Yet I had implemented without really realising - delete the cell and reload the section !
In effect I should and now have the following:
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
Like I said I've not had a problem since. Would love to know from anyone what inner workings would be responsible for this - the exception having been "Attempt to create two animations for cell".
Thanks for any interest.

Related

OSX NSTableView insertRowAtIndexes

i already checked Using NSTableView insertRowsAtIndexes solution but It does not solve my problem.
i want insert row in nstableview at particular index(add dynamically)
index Set is valid, still it causes Program crash
NSIndexSet *indexSet=[NSIndexSet indexSetWithIndex:i];
[myTableView insertRowsAtIndexes:indexSet withAnimation:NSTableViewAnimationEffectFade];
1)is any thing wrong in my code?
2)Is there any another way to add row at particular index(add dynamically)?
The code is correct but first you have to insert the model object in the data source array to keep model and view in sync.
I got My mistake..... problem In other Code so This code is fine
But I want to add some points About insertRowsAtIndexes: method
Hope it will helps to other people
1)Dont called reloadData() because you are adding particular number of rows so calling reloadData() will reload all data and it will causes crash
2) Calling this method multiple times within the same beginUpdates and endUpdates block is allowed, and changes are processed incrementally
3)Most Important thing is indexSet must be within range
if you are Enter valid indexSet then The numberOfRows in the table view is automatically increased by the count of indexes.
4)you can select animation according to your need
Sample Code :
[yourTableView beginUpdates];
NSIndexSet* theIndexSet = [NSIndexSet indexSetWithIndex:[self.yourTableContents count]-1];
[yourTableView insertRowsAtIndexes:theIndexSet withAnimation:NSTableViewAnimationEffectFade];
[yourTableView endUpdates];

Deleting A TableView Cell Populated By A Dictionary

So, deleting a table view cell is pretty straight forward. You remove the object from the array, and reload the table views data. Now... I have a table view that is populated from an NSMutableDictionary rather than array (we need to because we are parsing data and storing it). Does anyone know how to delete a cell via this method? I've tried simply calling [tableView reloadData]; which surprisingly worked, but obviously created an endless loop. I do not like endless loops. Does anyone have any suggestions? I couldn't find any relevant information in my research.
This is some relevant code in the cellForRowAtIndexPath delegate:
if (![frString isEqual:#"COOL STUFF"] && [subString isEqualToString:#"COOL"]) {
NSLog(#"A COOL is showing where it shouldn't, and it's %#", subString);
//remove all cells starting with COOL
[myDictionary removeObjectForKey:[self.dictName objectAtIndex:row]];
[tableView reloadData];
}
Also, I'd rather hide the cell instead of permanently removing the info from the dictionary. Is this possible?
There are a few things you're doing wrong :
You shouldn't remove the rows in the cellForRowAtIndexPath, because that's called when the tableview wants to render. So you remove a row, and then it renders again, calls cellForRowAtIndexPath:,...
Either delete the rows, or reload the data. deleteRowsAtIndexPaths: deletes sets of rows with animation, reloadData reloads the whole tableView, so it cancels the animation, and deleteRowsAtIndexPaths: becomes pointlesss
Ensure that you return the proper numberOfRowsInSection: did you remove the data from your dictionary ?

NSArrayController Entity Attribute Mysteriously Dropped

I have an NSArrayController bound to a Core Data entity — Channel. I have a method that updates the entity after a user changes the title of the Channel:
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextForCurrentThread];
NSString * savedTitle = [fieldEditor string];
self.mainWindowController.selectedChannel.channel_name = savedTitle;
[localContext MR_saveToPersistentStoreAndWait];
(I'm using MagicalRecord, in case that's not clear)
By all accounts, this save works — the value is reflected when the change is made, and if I immediately check the persistent store using Core Data Editor I can see the change has been saved.
The problem occurs when I change the view — the change is made in a master view, and the user can switch to a detail view. This line of code, which swaps out the master view, marks the spot where trouble occurs:
[[self.chatsViewController.view animator] removeFromSuperview];
After this line executes, the newly-saved value is gone. Requests to the entity attribute returns an empty string.
But here's the weird part. If I restart the application, the new value is still there! It comes back, and this time it's here to stay. Also, looking at the persistent store, I never see the value disappear at any point. This suggests that my array controller is getting the value taken away somehow.
I've tried refreshing the array controller after the save to "lock it in", as it were. But this appears to have no effect.
Can anyone suggest what steps I might take to track this down? I can't fathom why removing a view would cause this to happen.
Update
As I noted in my comment below, I tried putting a log statement on the name attribute's setter in my entity subclass, and it yielded a log entry when I changed the name, but never after.
I've now done another experiment: looking at the Core Data record both before and after the view switch.
NSArray * channels = [Channel MR_findAll];
NSLog(#"Channel: %#", [[channels objectAtIndex:0] channel_name]);
[[self.chatsViewController.view animator] removeFromSuperview];
NSArray * channels2 = [Channel MR_findAll];
NSLog(#"Channel2: %#", [[channels2 objectAtIndex:0] channel_name]);
The result:
Channel: adfasd
Channel2:
I have to be missing something here.

How to reload a UItableView with rows and section

I have a UITableView displaying a list of plants in alphabetical sections. Within the app, the user can change the language. When the language is changed, the method:
[self.tableview reloadData];
now displays the same plants in a new language. The problem is that the rows and sections are intact, and still sorted in the alphabetical order of the previous language. I have googled and read up and down stackoverflow to find a solution. The closest I came was:
[[self tableView]reloadData];
NSRange range = NSMakeRange(0, [self numberOfSectionsInTableView:self.tableView]);
NSIndexSet *sections = [NSIndexSet indexSetWithIndexesInRange:range];
[self.tableView reloadSections:sections withRowAnimation:UITableViewRowAnimationFade];
Still, I have to run the tableView through viewdDidLoad to change the alphabetical sorting.
What is the best way to force a new alphabetial sorting after [[self tableview]reloadData]; ?
Before calling reloadData you first need to update all of your data used by the table view data source and delegate methods.
You do not need to call viewDidLoad again. Put the code to setup the data structures in its own method. Then you can call this setup method from viewDidLoad (if needed) as well as just before calling reloadData after a language change.

How do I efficiently update a UITableView with Animation?

My iPad app features a UITableView populated from a feed. Like most RSS readers, it displays a list of links to blog posts in reverse chronological order, with their titles and a summary of each post. The feed updates frequently, and is quite large, around 500 posts. I'm using libxml2 push parsing to efficiently download and parse the feed in an NSOperation subclass, constructing entry objects and updating a database as I go. But then I need to update the UITableView with changes.
So far, the app has been updating the UITableView for every post parsed, as it is parsed. The parser performs a selector on the main thread to do this work. But this leads to some serious lag for a couple of seconds if a lot of cells need to be updated. I can mitigate this by running the table update on a background thread, but it seems that this is not a good idea. So now I'm trying to figure out how to update the table more efficiently on the main thread.
I could just call reloadData when all the posts have been parsed, but it's not very user friendly: there's no animation to indicate that anything has changed, just a flash and the new data is there. I'd much rather have it animate to show that new posts are added and old posts removed. Existing posts that are not removed from the feed should be pushed down the table by the new posts appearing at the top.
I know this is possible. Byline, to give one example, does a beautiful job. Each post is added or removed from the UITableView one-at-a-time with no gaps showing the table background. All without making the UI in the least bit unresponsive. How is that done??
My latest attempt is to update the table only after all the posts have been parsed (the parser is quite fast, so it's not much of a delay). It then loads the existing posts in an NSDictionary mapping their IDs to their indexes in the array used as the table data source. It then iterates over every object in the newly-parsed array of posts, adding NSIndexPath for each to arrays that are later passed to -insertRowsAtIndexPaths:withRowAnimation:, -deleteRowsAtIndexPaths:withRowAnimation:, and -reloadRowsAtIndexPaths:withRowAnimation: as appropriate to insert, remove, move, or update cells. For 500 posts, this takes around 4 seconds to update, with the UI completely unresponsive. That time is used almost exclusively for the UITableView animated updates; iterating over the two arrays of posts takes very little time.
I then modified it so that those are updated without animation, and I have separate arrays to insert/delete/reload with animation only for row positions corresponding to currently-visible rows. This is better, but gaps appear as posts are removed and new ones added.
Sorry this is so long-winded, but here's the upshot:
How can I update a UITableView, with new cells pushed on, others pushed off, and still others moved from one position to another, with up to 500 cells in the UITableView (6-8 are visible at one time), and each animation happening in sequence, all while the UI remains completely responsive?
This question actually has three answers. That is, there are three parts to this question:
How to keep the UI responsive
How to keep the updating fast
How to make table update animation smooth
UI Responsiveness
To solve the first problem, I now make sure that no more than one table-updating message can be delivered on each iteration of the main event loop. That prevents the main thread from locking up if the background thread is feeding it stuff to do faster than it can cope with it.
This is done thanks to example code sent to me by Byline author Milo Bird, which I then integrated into Dave Dribin's DDInvocationGrabber. This interface makes it super easy to queue a method to be invoked on the next available iteration of the main event loop:
[[(id)delegate queueOnMainThread]
parserParsedEntries:parsedEntries
inPortal:parsedPortal];
I quite like how easy it is to use this method. The parser now uses it to call all of the delegate methods, most of which update the UI. I've released this code on GitHub.
Performance
As for performance, I was originally updating one UITableView row at a time. This was effective, but somewhat inefficient. I went back and studied the XMLPerformance example, where I noticed that the parser was waiting until it had collected 10 items before dispatching to the main thread to update the table. This was key to keeping the performance up without making the UI lock up by updating all 500 rows at once. I played around with updating 1, 10, and all 500 rows in a single call, and updating 10 seemed to offer the best tradeoff between performance and UI lockup. five would probably work pretty well, too.
Animation
And finally, there's the animation. Watching the “Mastering Table Views” WWDC 2010 session, I realized that my use of the deleteRowsAtIndexPaths:withRowAnimation: and updateRowsAtIndexPaths:withRowAnimation: methods was wrong. I had been keeping track of where things should be added and removed in the table and adjusting the indexes as appropriate, but it turns out that's not necessary. Inside a table update block, one only needs to reference the index of a row from before the update, regardless of how many may be inserted or deleted to change its position. The update block, apparently, does all that bookkeeping for you. (Go to about the 8:45 mark in the video for the key example).
Thus, the delegate method that updates the table for the number of entries passed to it by the parser (currently 10-at-a-time) now explicitly tracks the positions of rows to be updated or deleted from before the update block, like so:
NSMutableDictionary *oldIndexFor = [NSMutableDictionary dictionaryWithCapacity:posts.count];
int i = 0;
for (PostModel *e in posts) {
[oldIndexFor setObject:[NSNumber numberWithInt:i++] forKey:e.ident];
}
NSMutableArray *insertPaths = [NSMutableArray array];
NSMutableArray *deletePaths = [NSMutableArray array];
NSMutableArray *reloadPaths = [NSMutableArray array];
BOOL modified = NO;
for (PostModel *entry in entries) {
NSNumber *num = [oldIndexFor objectForKey:entry.ident];
NSIndexPath *path = [NSIndexPath indexPathForRow:currentPostIndex inSection:0];
if (num == nil) {
modified = YES;
[insertPaths addObject:path];
[posts insertObject:entry atIndex:currentPostIndex];
} else {
// Find its current position in the array.
NSUInteger foundAt = [posts indexOfObject:entry];
if (foundAt == currentPostIndex) {
// Reload it if it has changed.
if (entry.savedState != PostModelSavedStateUnmodified) {
modified = YES;
[posts replaceObjectAtIndex:foundAt withObject:entry];
[reloadPaths addObject:[NSIndexPath indexPathForRow:num.intValue inSection:0]];
}
} else {
// Move it.
modified = YES;
[posts removeObjectAtIndex:foundAt];
[posts insertObject:entry atIndex:currentPostIndex];
[insertPaths addObject:path];
[deletePaths addObject:[NSIndexPath indexPathForRow:num.intValue inSection:0]];
}
}
currentPostIndex++;
}
if (modified) {
[tableView beginUpdates];
[tableView insertRowsAtIndexPaths:insertPaths withRowAnimation:UITableViewRowAnimationTop];
[tableView deleteRowsAtIndexPaths:deletePaths withRowAnimation:UITableViewRowAnimationBottom];
[tableView reloadRowsAtIndexPaths:reloadPaths withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
}
Comments welcome. It's entirely possible that there are more efficient ways to do this (the use of -[NSArray indexOfObject:] is particularly suspicious to me), and that I may have missed some other subtlety.
But even so, this is a huge improvement for my app. The UI now stays (mostly) responsive during a sync, the sync is fast, and the table update animation looks just about right.
Have you tried [tableView beginUpdates]; and [tableView endUpdate];?