Updating/Inserting existing objects in NSFetchedResultsController - iphone

I'm using NSFetchedResultsController with a UITableView which displays a list of folders and associated unread counts for each item in that folder. I'd like to insert/delete folders w/ animation from the tableView based on the unread count during a sync/refresh (on a background thread utilizing NSManagedObjectContextDidSaveNotification). Folders with no unread items should fade out, and existing folders with new unread items should fade in.
Currently using the controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: method does not do this unless the tableView is completely reloaded, or the app is relaunched, which is not ideal.
Is NSFetchedResultsController not the way to do this? My initial idea was to create a separate Core Data entity that only holds the folders with unread items, and add/remove folders from that, but that just seems hokey.
My NSPredicate looks something like:
ANY items.unread == 1
Update:
The above NSPredicate works fine and grabs the objects that I expect it to. My problem is that if the app launches and FolderX has 0 unread items, it does not appear. If I then refresh (goes out and parses a JSON file in a background thread) and FolderX now has 25 unread items, it will not automatically fade in or trigger NSFetchedResultsChangeInsert and since it wasn't included in the first place, it also doesn't trigger NSFetchedResultsChangeUpdate which is why I thought the solution was to create a separate Entity that only holds Folders with unread items and add/remove from that during sync.
I just feel like I'm missing something painfully obvious.
Update 2:
I've simplified the problem as much as I can by removing the influence of my own app, I'm able to replicate it in the default Core Data template in Xcode.
The New Problem:
There is a single Entity named "Event" with no relationships and two properties, "Date" and "unreadCount". I have NSFetchedResultsController set up with an NSPredicate that looks like:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"unreadCount < 10"];
Every time a new object is added, it is created with an unreadCount of 15, then all existing objects are decremented by 1. This is done on a separate ManagedObjectContext (to replicate background processing, but is all done on the main thread).
I've implemented the controllerWillChangeContent and related methods, and what I expected to happen was that after an existing object (with unreadCount of 15) is decremented 5 times (to now be 10) it should be inserted into the tableView because it now matches the predicate. This is not at all what happens, the object does not appear at all unless the app is restarted.
I've attached a sample project, simply run it and click the Add button a few times (nothing will happen). Restart the app and you will see a handful of objects now in the tableView, if you click the Add button a few more times those objects will remove themselves, but new objects will not get added.
If NSFetchedResultsController is not the solution to get this behavior to work, then what is?
Sample Project: http://dl.dropbox.com/u/521075/BGUpdating.zip

Your predicate will not work crossing two or more to-many relationships. You have at present:
type == folder AND ANY (set of folders).(set of items).unread == 1
You can't use ANY to transverse an arbitrary number of relationships. Instead, you probably need a subquery like:
type == folder AND (0!=SUBQUERY(folder,$f,0!=SUBQUERY($f.items,$i,$i.unread==1).#count).#count)
I think you maybe performing the fetch on the wrong entity. If you are presenting a table of Folder objects, you should be performing the fetch on the Folder entity. That alone would simplify everything. Your predicate would then be just:
ANY items.unread==1

Related

How to remove only entities saved to store from Core Data?

I'm trying to build a caching system for a feed reading application. The idea is each time a new feed is successfully pulled, remove all stored entities in Core Data, and store the first twenty items of the feed (this is used as an offline cache).
The issue I'm running into is my managed object context may have hundreds of items in it when a pull to refresh is performed. I'd like to keep those items in the context while removing any stored items from Core Data and then store the twenty items returned from the refresh call.
For what it's worth, I'm using Magical Record. I've tried looking around for this solution, but either I'm using the wrong keywords or the information is hard to find.
I'm not sure what code to show exactly, but here's the handling of the feed call:
for (id dict in feedArray){
WFeedItem *item = [WFeedItem feedItemWithAttributes:[dict dictionaryByReplacingNullsWithBlanks] inManagedObjectContext:[NSManagedObjectContext defaultContext]];
[parsedArray addObject:item];
}
This gets passed back from the subclassed HTTPClient it's defined in to a view controller that has called it. Bear in mind, this all works fine, it's all a matter of deleting stored items while retaining everything I've gathered during this session in the context.
Just use a different context for importing and storing the new records. Your original object context can remain as it is.

Pass complex object through controllers (core data)

I am completely lost.
The problem is passing data through viewcontrollers in my wizard.
My project contains 4 viewcontrollers:
Step1ViewController, possibility to fill in name
Step2ViewController, possibility to fill in nickname
Step3ViewController, possibility to fill in emailaddress,
Step4ViewController, possibility to fill in interests, finish
All viewcontrollers are pushed to a navigation controller. It's possible to go to the next step by clicking the bar button on the navigationtoolbar. What I want to achieve is to collect all the data in the steps and create save a NSManagedobject in the last step (by clicking the finish button). So when an user quits in step 2 and he restarts the app, there should be no saved object. So he will restart the wizard. When there is a personobject in core data then another view is loaded (this is a condition in the delegate class)
I know when you have a simple model schema passing data can be easily done to the controllers by using the prepareForSegue method. Collect all variables and create and save a core data object. For passing data back to the previous step I can use protocols.
But in my application my model schema is way more complex. My wizard contains about 18 steps and there are a lot of assiociated models for the Person model (like trainingsplan, interests, etc.) so I think collecting all data in variables and combine them all in the last step is really not a good approach.
What is the best way to do this?
I uploaded a wizard sample application with a couple of steps and 2 models (Person and interests (one-2-many)). Hopefully this will make it more clear. Feel free to modify the code: https://github.com/stalkert/WizardPrototype
Two ideas come to mind - each has their own pros and cons. Both use the concept of a mutable dictionary that will hold all the various data that you need at the end. In addition, you will add a key='step' with an NSNumber object.
1) Assuming that all the view controllers do not exist initially, he first controller creates the dictionary, adds the data it should supply to it, and sets the 'step' to two. It then creates and runs a new viewController, passing the dictionary off to it in a property. The second verifies the step is correct, adds what it should, then passes it to another controller. The 'step' here acts as a test that in fact the dictionary is at the stage it should be.
2) Assuming the view controllers are already instantiated then use the same 'step' concept as above, but use notifications. When the first step is complete, either store the dictionary in defaults, a class or singleton object, or the appDelegate (in a property). Send out a notification - and add a userInfo that is either the full dictionary or a number providing the next step. The controller that should do step 3 can see that its turn is up so it should become active (switch a UITabBarController tab automatically etc).
In either case, when the last step is complete, send the dictionary to the class managing the repository or do it directly.

An event for "this row was just loaded" in GWT cell framework

I have a list of ids of rows that should be selected, but not the actual objects that will be selected. For example, I know Users 16 and 25 should be selected, but I don't have an instance representing them. This could be because they're on a different page of data that I haven't loaded yet.
I want to be able to select these users programmatically even though their data is not loaded yet. I'm implementing a function called setSelectedIds() and it's working great - I scan all visible objects, and if their id matches one of the ids in my set, I set it Selected. Likewise, if the user changes a selection through the human interface, I catch the SelectionChangeEvent and determine whether an id should be added or removed to my backing list of ids.
The actual question:
Is there an event that's always fired when data has been loaded via updateRowData()? The only thing missing from my implementation is a way to handle the loading of new data. I need to be notified when new data is loaded, so I can decide whether to select it or not. RangeChangeEvents happen to soon - those handlers are fired before the data is loaded, and selectionModel.getSelected() returns some null objects. RowCountChangeEvents only happen when the total number of rows changes. What am I missing?
Can't you implement your own SelectionModel? When it's asked whether an object isSelected, it compares its ID with your list of selected IDs. You could even generalize it by using the object's key (given by the ProvidesKey) rather than an hard-coded getId.

How to automatically delete oldest core data entries when reach 50 entry limit?

What I'm trying to accomplish is the following: I need to limit the amount of core data entries to 50. So if the user enters their 50th entry then the app would delete the oldest entry and add the new entry to the top of the stack. So basically, if the user never deletes entries and if there are 50 entries in core data then, when the user tries to add a new entry, the app would delete the oldest entry and add the user's new entry. Basically, I'm trying to have a history sort of thing but I don't want the user to be able to go past 50 entries however I want them to be able to add new entries when their at the 50 limit by just dropping the oldest one and adding the newest one. What would be the easiest way to do this? I'm new to core data and having a hard time understanding a lot of it. Here's the code / example app that I'm working with. LINK TO EXAMPLE APP THAT I'M USING Thanks for the help.
Let's say you have an entity called History. The easiest solution would be to add a creationDate attribute to your entities. Then use that to manage your History objects.
You will need three fetches:
The first one will fetch as faults all the existing History objects and then count them. If the count is <50, then just add the new History object and your done.
If the count>=50, then do a fetch for specific value and use the #max or #min (I forget which for dates) collections operator to find the oldest creationDate. (As luck would have it the example at the link it pretty much exactly what you need.)
Perform a fetch for the object with the creationDate returned by (2) and delete it.
Then add the new history object.
OK, that's fine. CoreData is not going to do this for you, but you can do it yourself.
You can retrieve objects from you context using an NSFetchRequest, and you can delete them using -[NSManagedObjectContext deleteObject:]. You can sort them using NSSortDescriptor objects.

FetchedResultsController and UITableView in multiple views

Summary: I have two different views which use tables to show results from a fetched results controller. These tables may contain the same data. I get errors when moving the rows in one table after the other table has been loaded. (Obvious I suppose!)
To simplify, imagine an entity consisting of countries, and another entity grouping these countries together into sets of countries. We have an "editSet" view which allows you to name the set, add or delete countries in the set, as well as reorder them, all using the standard UI. We then have a "viewSet" view that shows you these countries and some value associated with them (eg. exchange rate or whatever).
Now, in the edit view, when we reorder and moveRowAtIndexPath is invoked, I set a BOOL which stops any further UI changes until the coredata is updated (each record has a displayOrder integer which I update before doing another performSearch). This all works perfectly if you only have instantiated the "editSet" view.
Where things goes wrong is if you load "viewSet" then load "editSet" with the same set and move the rows. The BOOL we set in editSet doesn't get passed along to viewSet (which is also "watching" the coredata) and gets upset when the coredata is changed programmatically. This generates:
Serious application error. An exception was caught from the delegate of
NSFetchedResultsController during a call to -controllerDidChangeContent:
. *** -[NSMutableArray removeObjectAtIndex:]:
index 0 beyond bounds for empty array with userInfo (null)
And all hell breaks lose.
On the other hand, if I load/appear "viewSet" with A DIFFERENT SET to the one I am editing, no problems.
So, what I need to do is EITHER "disconnect" the FRC and the table when leaving viewSet (perhaps do a search on nothing and reload the table?) OR pass a BOOL to the viewSetController when saving the moved rows in coredata in editSet to mimc what I do locally in that viewcontroller (not quite sure how to do this but doable I guess).
I am sure I am not the first to come across this problem, so wondering, what's the best way?
Use another managed object context for your "edits" and then merge them back in when you go back to your "viewSet". Take a look in the Apple sample code project 'CoreDataBooks' to see how you can use two contexts to perform disjoint edits.