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.
Related
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.
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.
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
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.
Ok here goes my setup:
Object Model:
* Category object is at the root
* Product is linked to a Category (many-to-1)
* ShoppingItem is linked to a Product (many-to-1)
Application setup:
My application has a TabBarController at the root and a tableviewcontroller at the root of each of the tabs.
The first tableviewcontroller shows all ShoppingItems
It uses a NSFetchedResultsController to retrieve the data
The sortdescriptor for this controller is set as "Product.Category.Name"
The sectionNameKeyPath is set as "Product.Category.Name"
There's no predicate set
Cache name is set for this NSFRC
This helps me group the shopping items based on its product categories into sections.
The second tableviewcontroller shows all Products
It uses a NSFRC as well to retrieve the data
Sortdescriptor -- "Category.Name"
SectionNameKeyPath -- "Category.Name"
The predicate set is "Active == 1" - to load only active products
Cache name is set for this NSFRC as well
This helps me group the products based on its categories into sections.
Scenario:
When I change the category associated with a product through a third screen, the tableview which displays products refreshes itself appropriately, by reslotting the product into a different (and correct) section
But the same isn't happening on the tableview which displays the shopping items
It is not an issue with the predicates I guess, as the problem is not about missing items, it is more about the items slotted in the wrong section.
Both the tableviewcontrollers are setup the same way to be notified by the NSFRCDelegate when changes to section and row info happen.
Any clues as to what's happening here? Is this behaviour correct?
P.S. : Am # work and not able to post code snippets. Can go home and do it, if that would help.
Caching could cause a temporary problem but if you make changes to the context, the cache should be refreshed automatically. You can test for this problem by deleting the FRC cache in the view controller's viewWillAppear method. That way when you come back to the view it starts with a fresh cache every time.
I think it more likely that you have two different managed object context. You have one context for the Product table and another shared by the Change-view and ShoppingItems table.
If the ShoppingItems table and the change-view share a context, then any alterations done in the change-view will appear automatically in the ShoppingItems table but the alterations will not appear in Shopping Items table unless you merge the context's changes.
If pretty much has to be separate context because the ShoppingItems can only get its category name by walking the relationship across the Product. If ShoppingItems shows the correct category but Product that must mean that each table is accessing different versions of each Product object.
Strange problem, to say the least. Could it be that NSFetchedResultsController doesn't get notified of changes to section when using a section name key path of more than 2 levels ?
You could try the following workaround to check this point :
Add a readonly property named "categoryName" to your ShoppingItem class, with following implementation :
- (NSString *)categoryName {
return self.Product.Category.Name;
}
Then, add the following method to your ShoppingItem implementation :
+ (NSSet *)keyPathsForValuesAffectingCategoryName {
return [NSSet setWithObjects:#"Product.Category.Name", nil];
}
This will ensure that each time the name of the category changes, the KVO system will also trigger a change notification for categoryName, thus hopefully getting the NSFRC notified of the change. Of course, use categoryName as the sectionNameKeyPath of your NSFRC.
Let me know if this works.
try using different cache names for different configuration (i.e, different sort or predicate) - it should be able to return correct data. It worked from me.