Pass complex object through controllers (core data) - iphone

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.

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.

Better way to loading Table with data coming from server.

I am implementing tableView which loads data from server. I got two scenario:
Get complete data from the server and store in the array say 500 items. Now whenever I need those data I will call my array and fetch data accordingly. In this case, loading huge data from server at first time will take place.
Get required data say 15 from the server and store in the Mutable array. And, if User has scroll down ,get data from the server again and add those in mutable array and display those in the table accordingly. In this case, whenever user will scroll up and down, we have to call server like Lazy Loading.
So, which would be appropriate way to load table from server.Any Feedback will be appreciated.
I think you should be use Lazy Loading. Pull To Refresh.... tableview is available for that. every refresh get 15 records and add into your array. i think this is the best way. because if you load 500 items at a time and any user required only 5th item, so other record are not useful..so i suggested you to use 2nd way.
I think for better solution you can have a bottom cell named "Load more items", click on that cell will load next set of items, it will help both user and programmer for avoiding unwanted items.

When to call obtainPermanentIDsForObjects:?

I'm currently having an issue where creating a new object on a background child thread (whose parent is the main UI thread context) and saving causes my NSFetchedResultsController to show two new objects: one with a temporary objectID, and one with a permanent objectID. This seems to be a bug of some sort, unless I'm missing something.
So I thought I would manually obtain permanent IDs for any new objects I create. This fixes the duplicate row issue, but introduces new random errors (such as "could not fulfill fault for object", refering to the new object I created). If anyone has any ideas as to why any of the previously mentioned is happening, please share.
I'm guessing obtainPermanentIDs is a step in the right direction. But when do I call this method? Before saving to the child context? After saving the child and before the parent? After the parent?
Currently my setup is this:
masterMOC - private queue tied to the persistent store, so physical saves happen here
----mainMOC - main queue tied to the UI, child of masterMOC
-------backgroundMOC - private queue, child of mainMOC
So if I create a new object on backgroundMOC, and I intend to immediatly save to disk (which means I'll have to call save: on all three contexts), where should I be calling obtainPermanentIDs?
(or if anyone has a different solution other than calling obtain permanent ids? What problem was this method introduced to solve anyway? Why would I want to call this method?)
Update:
I think I figured out what's going on (it's only a theory though), though not how to solve it. Core Data apparently generates permanent IDs for objects when they are saved physically to disk. So in my case, this won't happen until I call save on the masterMOC. Currently what I do when creating a new object on the backgroundMOC is:
save on backgroundMOC (so that changes are pushed up one level to the mainMOC and the my table view can insert the new rows)
save on mainMOC (so that I can prepare for saving to disk)
save on masterMOC (which finally saves to disk)
What's happening here is that calling save on the backgroundMOC triggers a UI update, and causes the fetched results controller to insert a new object that still has only a temporary ID. But then calling save on masterMOC causes all objects to get assigned permanent IDs, which causes another UI update, inserting another row for this "new" object! By commenting out the last masterMOC save, I no longer see duplicate entries. Am I doing something wrong here, or is this some kind of bug?
Another update: I think I've pretty much confirmed the bug. I call save on the backgroundMOC and then set up a timer to call save on the mainMOC and masterMOC 5 seconds later. Immediatley upon saving to the backgroundMOC, a new row is inserted into my table. 5 seconds later (upon saving main and master), another new row is inserted. (the row inserted first has a temp id, and the newest insert has permanent id).
I had the exact same issue, of course after a particularly difficult and dispiriting day of debugging everything to find out the issue was temporary IDs. :)
I have the exact same structure as you, and I also have subclasses of NSManagedObjectContext to codify the behavior I expect of saves in the background and main contexts – namely, a save in the background context should save the main context (and the main context should sync any objects that changed with the external service, which is irrelevant but worth mentioning as an explanation for why I have two subclasses), and a save in the main context should save the master context.
In my RFSImportContext subclass (equivalent to your backgroundMOC), I implement - save: to call [super save:], then call [self.parentContext performBlock:] (self.parentContext here is equivalent to your mainM)C, where the block calls obtainPermanentIDsForObjects: with the contents of the main context's - updatedObjects and - insertedObjects arrays, then I save the main context.
I no longer have the leaking of temporary objects into my NSFetchedResultsController as you describe. A way to improve the situation a bit would be to use the RFSMainContext subclass (again, equivalent to your mainMOC) to implement - save: to obtain permanent object IDs, save itself, then save the master context. This codifies the behavior that we always want the main context to have permanent IDs for objects in it when it is saved.

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.

Updating/Inserting existing objects in NSFetchedResultsController

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