CoreData model with nested object relationships as a datasource? - iphone

I have a very simple data model that consists of 2 objects — a Section and an Item. Each Section has a to-many child relationship to other Section objects as well as a to-one parent relationship with another Section object. Every section has a to-many relationship to Item objects.
Structure aside, some Section objects have no Item objects, and others (at the bottom of the hierarchy) have no Section child objects.
I want to create a tableview that will use Section objects to create the section headers, and then display the Item objects as tableViewCells that are a part of that Section. I also want table headers to appear if the Section has no Items, because seeing the hierarchy is important.
Given a random Section object, how would I go about fetching and displaying this data? Do I need to create a nested loop that flattens the data in an array, or is there some awesome way to leverage predicates and NSFetchedResultsController?

I would build your NSFetchedResultsController with a sort descriptor that sorts the items by the section's ID.
Something like:
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"section.ID" ascending:YES];
This will give your NSFetchedResultsController all the correct items grouped by section. Then you just need to flesh out your table view datasource and delegate accordingly.

Set your fetch entity to Item
Provide the fetch two sort descriptors. The first should sort on section.ID and the second should sort on id. That will return an array of Item objects sorted first by section and then by their own id attribute.
When initializing the fetched result controller, set the sectionNameKeyPath parameter to section.ID. That will cause the section names to display as the Section.ID values.
That should give you a table like this:
Section.ID
Item.ID
Item.ID
Item.ID
Section.ID
Item.ID
.... and so on.

Related

NSFetchResultController: Fill TableView with fetched entity and its related entity

I have 2 entities. One describes the Section of the TableView (A Month its name, etc.) This entity is related with a one to many relationship to another entity which should describe the rows of the TableView.
I'm a bit confused how to get those entites by an NSFetchedResultController. As far as I now I can only fetch one relationship at the time. So which one should I get to fill the table properly?
If you're using NSFetchedResultsController, you fetch the objects you want to display in the table view.
To get sections, you use NSFetchedResultsController's sectionNameKeyPath property to indicate how to find a section name from one of the fetched objects. This key path is something you could pass to one of the fetched objects via valueForKeyPath: to get the section name. In your case it would require traversing a relationship back to the month entity (or whatever it really is) to get its name. For example if the relationship is called month and the Month entity has a name attribute, you would pass something like #"month.name" as the sectionNameKeyPath argument when you create the fetched results controller.
You can also use the excellent Sensible TableView framework to automatically fetch the Core Data objects and display them in a table view. The framework will also detect if the entities have any relationships and will automatically manage the detail view controllers between them.

Core data cross referencing two relationships

I have a data structure in Core Data like so...
User
Item
Category
User has a toMany relationship "FavouriteItems" to the Item entity.
Category also has a toMany relationship "Items" to the Item entity.
The user can select favourite items from any categories they wish. At the moment I am listing all the items and then displaying the Category alongside.
What I'd like to do is display all the user's favouriteItems for a selected Category.
i.e. select all the Items that have a relationship with Category x and User y.
I'm currently doing this by getting all the Items through one relationship (i.e. User.favouriteItems) and then filtering the NSSet using a block predicate.
Is it possible though to do this with a simple CoreData predicate?
Hmm... thinking about it would a predicate like this work...
[NSPredicate predicateWithFormat:#"interestedUser.id = %# AND category.id = %#", user.id, category.id];
And then run a fetch request on the item entity?
Would that work?
Shooting pretty blind as that's an awkward scenario to set up just to answer a question but perhaps
If you are filtering an array of Items which has the correct inverse relationships set up.
[NSPredicate predicateWithFormat:#"%# IN interestedUsers AND %# IN categories",
someUser,
someCategory];
Basically the Item has many users (interestedUsers) so we are saying is our user in this collection.
Similarly the Item has many categories (categories) so we are saying AND is our chosen category in this collection.

NSFetchedResultsController and child UITableViewController

Right now I have an UITableViewController that displays a set of Artist objects sorted into sections (alphabetically) using NSFetchedResultsController. When you tap an artist in that list, a second UITableViewController is pushed and displays the artist.shows objects, also sorted into sections (by date).
Right now I'm sorting that second data source (artist.shows) "manually", using NSSortDescriptor and a for() loop to determine where to have the table view sections (one section for every month).
Is that the right way to go? Would it make more sense to create a second NSFetchedResultsController to sort that data, although it would basically be fetching data I already have in artist.shows?
You should definitely use a second fetched results controller. It can share the managedObjectContext with the first, allowing to access the already faulted objects in that context.
You'll want to structure your predicate / sort descriptor for the controller appropriately, such as:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"artist.name == %#" argumentsArray:[NSArray arrayWithObject:selectedArtist.name]];
You could also scope it by the actual artist object but that should give you the idea.

UITableView and Core Data to-many relationships

I've got a problem with UITableViews and many-to-many relationships.
I have two classes, A and B, that both extend NSManagedObject and live in Core Data. Each has a to-many relationship to the other:
A<<-->>B
I would like to use objects of class A as the sections of a UITableView, and it's set of B objects as "data rows" in the TableView. Is this possible, and what would be the best approach to achieve this? Preferably I would like to use a NSFetchedResultsController to manage the data presented by the UITableView, but so far I have not been able to come up with a predicate and section name key path that works.
You can't use a fetched results controller to do this.
If the A objects are the section names and each B objects has many A relationships then each B can show up in the table multiple times in different sections. Core Data is really set up around the idea of unique objects and all the boilerplate code for tableviews assumes that each row represents a single unique object. In this circumstance, even counting the number of rows would be difficult and the sectionNameKeyPath would be impossible to set because it could have multiple values at any one time.
You need to configure all this by hand. You'll have to fetch the A objects and then set the section titles, then you will have to get each A.bObjs and count and sort them for the rows in each section completely individually. Deleting an exist B object might prove difficult because it could trigger the removal of multiple rows at the same time.
I would urge you to rethink your design. Sections and section titles are not supposed to represent managed objects but rather a grouping of the managed objects represented by the rows based on one of the row object's attributes. What you really want here is a master-detail view hierarchy. The top tableview will show all the A objects. When a row is selected, it loads the second tableview that shows all the B objects related to the selected A object.
I would use NSFetchedResultsController to watch over the A objects. Use your fetched results to populate the section titles. Then when you are looking to populate your "data rows", you can find your current A object (by using [[fetchedResultsController fetchedResults] objectAtIndex:indexPath.section] or something similar) and extract the B objects from it with A.BOjbects, which returns the NSSet of all B objects associated with the A object. After doing that, you can filter or sort the list as you please before placing them in the rows

Fetched Results Controller with NSPredicate vs. Core Data NSSet

Okay, so I have a UITableView hierarchy. The top level controller is for Categories, which are selected via Fetched Results Controller with no predicate for the Category Entity.
When a user taps a cell, they are advanced to the Items Table View Controller.
At this point, I assumed I should use another fetched results controller with an NSPredicate to filter out the results not matching the selecting category.
However, I've had a lot of difficulty building a predicate that does this. All the examples seem to be for search strings to attribute comparisons. Not comparing a CoreData Relationship.
So is it a better practice to use this method, or simply get the NSSet of items from the passed in Category Managed Object?
Your second level view controller should have the selected Category set into it via dependency injection as a property. Your second level view controller should only know that "it's" Category is X. It does not need a NSFetchedResultsController at all because you already have all of the information you require via relationships on "it's" Category entity.
To do this, in your top level view controller when a Category is selected you get a pointer to it via the NSFetchedResultsController, instantiate the new view controller, set the Category property and then push the new view controller.
This will give you solid incapsulation and separation between your view controllers.