I need help to think about and implement filtering of NSFetchedResultsController's fetchedObjects at the model layer.
Docs say:
Responding to Changes
In general, NSFetchedResultsController is designed to respond to changes at the model layer, by informing its
delegate when result objects change location or when sections are
modified.
NSFetchedResultsController Class Reference
For example, how could one implement search at the model layer?
I imagine a Filter entity which is somehow related to the entities being filtered, and is used in the NSFetchRequest's predicate.
Maybe I would create a Filter object, configure it with, say, a string from a search field, and then save the object. This would affect the NSFetchRequest's predicate, so the NSFetchedResultsController would get the private messages for updating its fetchedObjects and issue messages to its delegate.
I could create other Filter objects (or change existing ones) which further affects the NSFetchedResultsController's fetchedObjects, and that, when deleted, stop affecting fetchedObjects, which are then 'visible' again.
What would the model look like in Xcode's model editor?
What would the NSFetchRequest's predicate look like?
What are drawbacks with this approach?
Agree with Wain. The model layer is not the suitable mechanism to update a list when searching.
Instead, modify the predicate of the fetchRequest of the NSFetchedResultsController and re-fetch. This is a common pattern for implementing search with a UITableView.
This is unlikely to work. Your fetch would be directed at entity A and use a predicate related to entity B which holds some configuration that you update. The FRC is only monitoring changes to entity A, not entity B, so when you change the configuration it won't be picked up so no trigger for the delegate.
The only way to do it is to update the predicate and restart the FRC. The alternate being to somehow encode the information into entity A, but if you can do that you don't need the FRC as you already have all of the new search results before the FRC knows about it...
With this approach, you would need to create a Filter object for each result object for NSFetchedResultsController to pick it up. These kinds of tricks can be applied to modify user-driven sorting and sectioning but for filtering, you're better off using a predicate on NSFetchRequest which filters on actual model data.
Unless you want to do some very specific filtering which can't be done by using only the original model data. In that case it would be an alternative but you would still have to create a Filter object for each underlying data object.
Related
Is there a way to use a 'transient' field or something like that and in some way sort accordingly with a NSFetchedResultsController. I want to do the following:
I have location of places in a database. When a person opens the list, I want to show the nearest place on top, an then sort accordingly to distance. But clearly,this depends on the users location, so I cannot use a static field. I was hoping to use a transient field, as you can use for the section headers.
Is there anybody who can give a solution or workaround for this situation?
You cannot use a transient property in a fetch request for a SQlite base Core Data store.
See Fetching Managed Objects in the "Core Data Programming Guide":
You cannot fetch using a predicate based on transient properties
(although you can use transient properties to filter in memory
yourself). ... To summarize, though, if you execute a fetch directly, you should
typically not add Objective-C-based predicates or sort descriptors to
the fetch request. Instead you should apply these to the results of
the fetch.
You can use a transient property for sectionNameKeyPath, but even then you need a first sort descriptor for the sections that is based on a persistent attribute.
So the only workaround is probably to fetch all objects and then sort the fetched array. But then of course you don't have the advantages of a fetched results controller anymore.
I have a collection of CoreData objects that have a to-many relationship to another object type.
On some of these object I need to search through the related objects to find a particular one. So I loop through them looking for a match which works fine. But on closer inspection I can see CoreData firing off each fault off as it gets to each item in the loop, obviously this is no good - hundreds of faults fired off individually.
Can I trigger CoreData to fire all of the faults in that relationship as a group?
I don't want to just prefech the relationship in the first place because I am dealing with a very large number of objects and for almost all of them I won't ever need to drill down into the related objects.
You could use the inverse relationship to "manually" fetch the related objects, using a predicate to restrict the results. For example if Department has a to-many relationship to Employee and you want to fetch all the Employees for currentDepartment, the fetch might look like this:
NSFetchRequest *employeeFetch = [NSFetchRequest fetchRequestWithEntityName:#"Employee"];
employeeFetch.predicate = [NSPredicate predicateWithFormat:#"department == %#",currentDepartment"];
This will fetch the required Employee objects in one go (*). You could then use either the array returned by the fetch, or the set given by the currentDepartment.employees relationship to search through. Depending on the complexity of the search you are performing, you might even be able to express it as another clause in the predicate, and avoid the need to loop at all.
(*) Technically, the objects returned by the fetch will still be faults (unless you set returnsObjectsAsFaults to false), but the data for these faults has been fetched from the store into the cache, so firing the fault will now have minimal overhead.
I have a view that contains a tableView. I'm using NSFetchedResultsController to display my results from Core Data.
I'd like to add a UISearchController (not a UISearchDisplayController as this one is deprecated in iOS 8) but I don't know how to link them.
Should I have only one NSFetchedResultsController or two?
I guess there is a way to fetch all the data with the NSFetchedResultsController and then just sort them according to the UISearchController am I wrong?
Thanks for the help you can give me on this.
A few thoughts...
When you create the resultsController for the searchController, you can pass to it the data that you wish to search. If you are using an NSFetchedResultsController in your main table, you could pass the fetchedObjects array. Then in response to changes to the search text, you filter the array (you could use a predicate and filteredArrayUsingPredicate to create a separate array with the search results, or you could iterate through the array to build it). The disadvantages of this route are that (unless you implement it manually) the search results will not be broken into separate sections, and the search results will not update automatically if the underlying data changes (eg. on a background thread).
I guess you could have a second NSFetchedResultsController: that would facilitate using sections, and could potentially permit the results to be updated automatically (using the delegate methods) if your data is being updated in the background, for example. But I would be nervous of the complexity that it introduces.
Another option, if you opt to apply the search in situ (i.e. specify resultsController = nil), would be to use the search criteria to update the NSFetchedResultsController itself (i.e. amending the underlying predicate and reperforming the fetch). That way your search table looks and feels exactly like the main table (including sections, if you use that) but the rows displayed obviously reduce as the search criteria become finer. This option needs care to ensure that the FRC is properly rebuilt, and might be unacceptable performance-wise, if you have a large dataset.
I'm banging my head trying to figure out something in Core Data that I think should be simple to do, and I need some help.
I have a data store which contains data from the past two years, but in my app, I have certain criteria so that the user only works with a subset of that data (i.e. just the past month). I have created the predicates to generate the fetch request and all that works fine.
My issue is that I then want to run some additional predicates on this subset of data (i.e. I just want objects with name=Sally). I'd like to do so without having to re-run the original predicate with an additional predicate (in a NSCompoundPredicate); I'd rather just run it on the subset of data already created.
Can I just run a predicate on the fetch results?
Is the format of the predicate the same as for the initial calls into the core data store?
Thanks for any help.
You could filter the original array of results using a predicate. See the NSArray filteredArrayUsingPredicate method.
The only reason I would consider doing what you're talking about is if you find that modifying the predicate at the FetchedResultsController level caused significant performance degradation.
My recommendation is to get a reference to your fetchedResultsController in your view controller and update your predicate with a compound predicate matching your additional search parameters.
If you were to tie your viewControllers data source to a predicate of a predicate you wouldn't be able to properly utilize the NSFetchedResultsControllers Delegate methods that allow for easy, dynamic updating of Views such as table view and collection view.
/* assuming you're abstracting your datastore with a singleton */
self.fetchedResultsController = [DataStore sharedStore].fetchedResultsController;
self.fetchedResultsController.delegate = self;
[self.fetchedResultsController.fetchRequest setPredicate:yourPredicate];
Make sure to configure your fetch requests' batch size to a reasonable value to improve performance.
I'm using NSFetchedResultsController (NSFRC) to display information in a UITableView. I'm trying to create the option for the user to sort the cells in sections as opposed to alphabetically. The problem is, the sections would then be determined using downloaded information. On top of this the section for each item will be changing relatively often so I don't want to save the section. I have noticed the mention of transient attributes, in my research of similar problems, but i've never used these before I'm not sure if I can use them baring in mind that all the calculations are done once the data has already been loaded, and I also want this solution to be compatible with my previous Core Data database. Also I'm not particularly great at Core Data, (nor Objective-C at that!) so I'm not entirely sure how I'd go about doing this.
So here's what I want to go for if we're using transient attributes (this next bit is theoretical as I don't know if transient attributes are the correct way forward). I would like 4 possible sections, 0-3 (I'll rename them using the TableView delegate to get around sorting problems). When the calculations are done, each cell will be assigned the transient attribute (if needed, the default section would be 2). I hope this all makes sense.
Right, now for some theoretical code. First I create the transient property in the Data Model screen-thing, and make it transient by checking the transient check box... Sounds simple enough.
In the code for the calculations in willDisplayCell (needs to be done in wDC for a couple of reasons), the entity could be saved like this:
MyEntity *myEntity = [self.fetchedResultsController objectAtIndexPath:indexPath];
myEntity.sectionTransientProperty = 2;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Error: %#", error);
FATAL_CORE_DATA_ERROR(error);
return;
}
Done, right? Is that how we assign a value to a transient property?
Then I change the sorting option in NSFRC when I alloc it:
fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:#"sectionTransientProperty"
cacheName:#"MyEntity"];
How are we doing, what else do I need to do? Or have I got this so horribly wrong I should just give up on Core Data and NSFRC? If you guys could help guide me through this I'd really appreciate it. If you need me to post any more code I would be happy to.
Regards,
Mike
If you want an FRC with sections, you have to add a sort descriptor to the fetch request, and that sort descriptor cannot be based on transient attributes.
See the documentation of initWithFetchRequest:managedObjectContext:sectionNameKeyPath:cacheName:`:
If the controller generates sections, the first sort descriptor in
the array is used to group the objects into sections; its key must
either be the same as sectionNameKeyPath or the relative ordering
using its key must match that using sectionNameKeyPath.
and Fetch Predicates and Sort Descriptors in the "Core Data Programming Guide":
The SQL store, on the other hand, compiles the predicate and sort
descriptors to SQL and evaluates the result in the database itself.
This is done primarily for performance, but it means that evaluation
happens in a non-Cocoa environment, and so sort descriptors (or
predicates) that rely on Cocoa cannot work. The supported sort
selectors are ...
In addition you cannot sort on transient properties using the SQLite store.
This means that you cannot create sections purely on transient attributes. You need a persistent attribute that creates the ordering for the sections.
UPDATE: A typical use of a transient attribute as sectionNameKeyPath is: Your objects have a "timeStamp" attribute, and you want to group the objects into sections with one section per month (see the DateSectionTitles sample code from the iOS Developer Library). In this case you have
a persistent attribute "timeStamp",
use "timeStamp" as first sort descriptor for the fetch request,
a transient attribute "sectionIdentifier" which is used as sectionNameKeyPath. "sectionIdentifier" is calculated from "timeStamp" and returns a string representing the year and the month of the timestamp, e.g. "2013-01".
The first thing the FRC does is to sort all fetched objects according to the "timeStamp" attribute. Then the objects are grouped into sections according to the "sectionIdentifier" attribute.
So for a FRC to group the objects into sections you really need a persistent attribute. The easiest solution would be to add a persistent attribute "sectionNumber" to your entity, and use that for "sectionNameKeyPath" and for the first sort descriptor.