I'm building an iOS app using RestKit and NSFetchedResultsController in my ViewControllers like explained in the following tutorial : http://www.alexedge.co.uk/blog/2013/03/08/introduction-restkit-0-20
I have a subclass of a UITableViewController that displays the data fetched by the NSFetchedResultsController and it works well.
Now I want to filter the displayed data so I'm adding a predicate to the fetch request of the NSFetchedResultsController and performing the fetch again (I don't use any cache) :
[self.fetchedResultsController.fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"title like 'some text'"]];
NSError *error = nil;
[self.fetchedResultsController performFetch:&error];
NSAssert(!error, #"Error performing fetch request: %#", error);
No error happens but the table view is not updated. I was expecting this method to be called
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller;
but it's not...
Worse, when I log the number of fetched objects like this
NSLog(#"before refetch : %d objects", self.fetchedResultsController.fetchedObjects.count);
[...]
NSLog(#"after refetch : %d objects", self.fetchedResultsController.fetchedObjects.count);
I'm getting
before refetch : 18 objects
after refetch : 0 objects
(That's probably why my app crashes as soon as I try to scroll the table view)
So how should I refresh the content of the NSFetchedResultsController so that controllerDidChangeContent is called and thus I can reload the table view ? and also that fetchedObjects contains something...
EDIT:
If at this moment I call the RestKit object manager to get the data displayed in the table view AND a Internet connection is available THEN the NSFetchedResultsController gets updated and the table view as well with the correct result of the fetch request WITH the predicate.
EDIT:
The predicate I use is the cause of the problem (I think). My entity has a transformable attribute which is parsed from an JSON array of strings and the predicate test if a search string exists in that array:
[NSPredicate predicateWithFormat:#"ANY transformable_attribute like 'search string'"]
The weird thing is that if I load the results without predicate and add it afterwards then I get 0 object BUT if I load the results directly with the predicate then I get 3 objects.
Is my predicate wrong?
EDIT: yes it is! Looks like we can't predicate on transformable attributes. I don't know why it works the first time.
Your predicate is a constraint that leads to 0 results. That is probably expected behavior. Check what is in the data store and you will most likely find that it follows the logic of the predicate and your data.
You will need to change your predicate to get the results you intended.
Call
[self.tableView reloadData];
after performing the fetch to update the table.
Changing the predicate of a NSFetchedREsultsController does not result in a delegate call to controllerDidChangeContent. From the documentation:
An instance of NSFetchedResultsController uses methods in this protocol to notify
its delegate that the controller’s fetch results have been changed due to an add,
remove, move, or update operations.
The solution is to reload the table with
[tableView reloadData]
Related
A colleague and I are working on a messaging program for iOS. We store the messages using Core Data. If I understand the way things are supposed to work, whenever a new message is stored in the database, because we have configured our app to work with NSFetchedResultsController, Core Data is supposed to tell our chat view controller what change has been made using the method controller:didChangeSection:atIndex:forChangeType:. This works most of the time, but on some occasions said method does not fire. This has been particularly noted when multiple messages are dealt with in rapid succession. Even in said cases, controllerDidChangeContent: does fire, so Core Data does know something has happened. Does anyone have any idea what’s going wrong?
Note: I have devised a workaround in which whenever a new message is added to the database, an NSNotification of the addition is automatically posted; said notification is observed by the chat view controller and acted upon if appropriate. This works without creating new problems. However, my colleague is worried, because my solution subverts the way Apple intended us to handle database-view controller communication and thus might conceivably cause unexpected problems in the future.
Thanks in advance for any help anyone can provide.
UPDATE (2017-04-27): The code for the relevant predicate is:
// The predicate needs to be as follows:
// Select * WHERE (fromuser == userTelephoneNumber AND touser == friendTelephoneNumber) OR
// (fromuser == friendTelephoneNumber AND touser == userTelephoneNumber)
// First condition
NSPredicate *chatFromMe = [NSPredicate predicateWithFormat:#"fromuser == %#", self.userTelephoneNumber];
NSPredicate *chatToFriend = [NSPredicate predicateWithFormat:#"touser == %#", self.friendTelephoneNumber];
NSPredicate *chatFromMeToFriend = [NSCompoundPredicate andPredicateWithSubpredicates:#[chatFromMe, chatToFriend]];
// Second condition
NSPredicate *chatFromFriend = [NSPredicate predicateWithFormat:#"fromuser == %#", self.friendTelephoneNumber];
NSPredicate *chatToMe = [NSPredicate predicateWithFormat:#"touser == %#", self.userTelephoneNumber];
NSPredicate *chatFromFriendToMe = [NSCompoundPredicate andPredicateWithSubpredicates:#[chatFromFriend, chatToMe]];
NSPredicate *totalCondition = [NSCompoundPredicate orPredicateWithSubpredicates:#[chatFromMeToFriend, chatFromFriendToMe]];
NSFetchedResultsController will only react to -save: being called on the NSManagedObjectContext. So you may not see this method fire if you are not saving the newly created entity.
You have to re-fetch after a context change. So back to using the NSFetchedResultsController, you would do: - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { [self.fetchedResultsController performFetch:&error]; [self.tableView reloadData]; }
Also The general rule with Core Data is one Managed Object Context per thread, and one thread per MOC. With that in mind you need to perform the fetch for the Fetched Results Controller on the main thread, as this is the thread that will be interacting with the FRC's Managed Objects.
I am playing with an app that uses Core Data and NSManagedObjects to populate a UITableView. There is only one class in my application, called Event. I have created the following custom instance method on Event:
- (BOOL)isExpired {
return ([[self.endOn dateAtEndOfDay] timeIntervalSinceNow] < 0);
}
I would like to limit the UITableView that displays Event objects to only the Events that are expired - that is, where isExpired returns YES. I have tried to do this by adding an NSPredicate to the NSFetchRequest:
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary * bindings) {return([evaluatedObject isExpired]);}];
[fetchRequest setPredicate:predicate];
but I get the error: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Problem with subpredicate BLOCKPREDICATE(0x272ac)'
***. Does this mean that you can't use a block predicate with an NSFetchRequest? Or have I just constructed it improperly?
Thank you!
So, it appears that we've established in the comments to the original post that this is likely caused by SQLite stores being incompatible with block predicates, since Core Data cannot translate these to SQL to run them in the store (thanks, JoostK).
There might be a couple of ways to overcome this:
Provided that the end date of your entities is a regular attribute, you might be able to express the expiry constraint as a predicate format string instead of a block predicate, which Core Data should be able to translate into a SQL clause.
If the above is possible, you will probably prefer to use a fetch request template to retrieve the expired items. You would need to pass in a substitution variable like $NOW to give access to the current date, though. This has the advantage of making the predicate template show up in the model editor.
Both approaches, however, have the disadvantage of duplicating existing functionality (i.e., your isExpired method). So another way would be fetch all qualifiying entities regardless of their expiry state first, and then run a dedicated filtering step on the resulting set of entities to weed out the non-expired ones. Since by that point, they have been fully resurrected from the store, you should be able to use a block predicate for this.
You can do a normal fetch request without specifying the predicate, and afterwards filter the resulting array:
NSArray *allEvents = [context executeFetchRequest:fetchRequest];
if (!allEvents) { // do error handling here
}
NSArray *expiredEvents = [allEvents filteredArrayUsingPredicate:predicate];
I am attempting to get an object from a fetch request, and I am checking a property of that object.
Depending on what type the property is will cause me to display a notification or not. After making a successful connection, I set the property type of my object to 'updated' from 'inserted.' Then, when I refresh my view, I pull all objects from coredata and check their properties for the 'updated' type. The problem I am having is that the objects returned in my fetch request that I just attempted to change to 'updated' still display the old 'inserted' value from the fetch request, but don't immediately after the submission. Its like they are reverting. (and I AM saving the context)
What is even more confusing is I have gotten a program to look at the actual tables in the database file stored on the device, and it actually shows the correct value of updated in the table. But the fetch request still comes back with the object having incorrect data. And no amount of refreshing fixes the issue.
How can a fetch request be giving me objects with old/incorrect data when the coredata file shows the tables with correct values?
// code for the fetch request
// return an array of all assets for a specific customer
NSFetchRequest *fetchReq = [NSFetchRequest fetchRequestWithEntityName:#"Asset"];
fetchReq.predicate = [NSPredicate predicateWithFormat:#"customerID = %#" argumentArray:[NSArray arrayWithObject:customerID]];
NSArray *results = [[CoreDataManager sharedManager] executeFetchRequest:fetchReq];
return results;
//executeFetchRequest method
NSManagedObjectContext *context = [self managedObjectContextForCurrentThread];
NSArray *results = [context executeFetchRequest:fetchRequest error:&error];
return results;
#Arcanfel's suggestion (in comments to the original question) certainly helped me find the solution to my own problem, but it was hidden in the 'Show more comments' section.
It would have been helpful to see that as an answer, not a comment, so I've taken his comment and expanded it a little.
Original comment:
[self managedObjectContextForCurrentThread] - I think this can cause a
problem, as different contexts can have different version of
NSManagedObjects. You can call [fetchReq
setShouldRefreshRefetchedObjects:YES] and it will return the most
up-to-date version of your objects
Whilst using the setShouldRefreshRefetchedObjects:YES didn't resolve my problem of subsequent fetch requests not bringing back the right data, it did make me look at my context management.
In one method I was setting the context, and then reading data. In another method I was setting another context, reading data, altering it and then saving that context... a different context to what my first method had.
In short, be careful that you're referring to the same context or anything you update in context1 won't be in synch with what you retrieve from context2.
I have a custom UITableViewController, called mainViewController.
It has a public property in the header file
#property (strong, nonatomic) NSMutableArray *someGroups;
This property is set in the viewDidLoad method
_someGroups = [[GGGroups findAllSortedBy:#"lastUpdated" ascending:NO withPredicate:[NSPredicate predicateWithFormat:#"ANY users = %#", [GGUser currentUser]]] mutableCopy];
However, for some reason, if I ever call the following, once the view has loaded, it returns 0 objects.
[[GGGroups findAllSortedBy:#"lastUpdated" ascending:NO withPredicate:[NSPredicate predicateWithFormat:#"ANY users = %#", [GGUser currentUser]]] mutableCopy];
This is quite annoying especially when I want to reloadData in the table view controller, since this array is the data source, so the table view becomes blank since this method returns 0 objects now.
I am currently using MagicalRecord, does anyone know why this occurs.
Thank you!
There is nothing in MagicalRecord that caches your find* requests. Every call to any of the find methods will generate a new request and perform a new fetch on that new request. My guesses as to why your second fetch is failing:
The default context (which you are implicitly using by not specifying one) is nil somehow
Your currentUser method is returning nil
Your data store is empty on that second call
You aren't actually calling your method to reload data at the right time
You are not refreshing your view of data
MagicalRecord has logging built in. I suggest turning it on and seeing if there are any errors that show up in the logs. Those will help pinpoint any issues that might be arising with Core Data.
I have an NSFetchedResultsController displaying "places" in a table view, but when I update the set of places that should be displayed in another view controller, my FRC does not update the table view.
That's the general problem. My specific case seems to revolve around the NSPredicate backing my FRC, because when I remove the NSPredicate (and just get all places), everything works fine.
My query is
#"ANY photos.isFavorite == %#", [NSNumber numberWithBool:YES]
places have a one-to-many relationship with photos (I am working through CS193P). Perhaps my FRC is not set up to observe changes in a related table or something?
A bit of additional information about my situation:
My Core Data updates and queries seem okay, as my "places" table is always correct when I first load the application.
My FRC does update rows that are already present at application load. It just won't insert new rows/sections at runtime.
I am only using a single MOC.
My sectionNameKeyPath is not set to a transient attribute.
My cacheName is set to nil.
I don't know if it's your case but I'll post anyway. Maybe it could be a valid workaround.
NSFetchedResultsController pitfall
Hope it helps.
Did you try testing a simpler predicate, for example using a photo one-to-one relationship instead of photos?
"my FRC does not update the table view" - Are you using -controllerDidChangeContent: ? Did you verify it is not getting called? I've seen that contrary to the documentation, calling reloadData on the tableView from within this method is unsafe, since the method may be called on a secondary rather than the main thread.
Are you performing all operations related to the MOC on the same (main?) thread?
Try this predicate instead?
#"%# IN photos.isFavorite", [NSNumber numberWithBool:YES]