The problem:
When I do a total Core Data refresh via a pull-to-refresh control (deleting everything and then adding everything again), my automatic NSFetchedResultsController (CoreDataTableViewController) just deletes all rows and doesn't show the new data.
My setup:
I use a Core Data datastore which I fetch data from via the use of a subclass of CoreDataTableVewController. In this case, I have a list of groups the logged-in user is added to, which is fetched by using the predicate:
[NSPredicate predicateWithFormat:#"users CONTAINS %#", self.currentUser];
I then use a datastore refresh function (the same as on app startup) to refresh the data, via a pull-to-refresh control (SVPullToRefresh).
[self.tableView addPullToRefreshWithActionHandler:^{
DatabaseMerger *merger = [DatabaseMerger sharedInstance];
[merger refreshDatabase:self.context];
self.tableView.pullToRefreshView.lastUpdatedDate = [NSDate date];
}];
In this refresh, all the Core Data Managed Objects get deleted, and then added again with the use of JSON data.
With the use of the CoreDataTableViewController, which observes the context, the rows should be refreshed if the context changes. Currently, the only thing that changes, is that all the rows disappear.
The weird thing is, that when I pop the viewcontroller, and push it again (via the navigationbar's back-button), the rows are correctly displayed with the data. This seems to me that it is a problem with fetching the data realtime.
What I have tried:
Manually refetching the data via the use of [self.fetchedresultscontroller performFetch:&error]
Deleting the fetchedresultscontroller's cache via the use of [NSFetchedResultsController deleteCacheWithName:nil]
Setting the fetchresultscontroller to nil, and then setting the controller up again
Relevant Code:
- (void)setupFetchedResultsController
{
NSFetchRequest *groupRequest = [NSFetchRequest fetchRequestWithEntityName:#"Group"];
groupRequest.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES]];
groupRequest.predicate = [NSPredicate predicateWithFormat:#"users CONTAINS %#", self.currentUser];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:groupRequest managedObjectContext:self.context sectionNameKeyPath:nil cacheName:nil];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self setupFetchedResultsController];
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.debug = YES;
self.currentUser = [Settings currentUser:self.context];
[self.tableView addPullToRefreshWithActionHandler:^{
DatabaseMerger *merger = [DatabaseMerger sharedInstance];
[merger refreshDatabase:self.context];
self.tableView.pullToRefreshView.lastUpdatedDate = [NSDate date];
}];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveFinishedNotification:)
name:#"dataMergeFinished"
object:nil];
// Do any additional setup after loading the view.
}
- (void)receiveFinishedNotification:(NSNotification *) notification
{
if ([[notification name] isEqualToString:#"dataMergeFinished"]) {
[self.tableView.pullToRefreshView stopAnimating];
[NSFetchedResultsController deleteCacheWithName:nil];
NSError *error = nil;
[self.fetchedResultsController performFetch:&error];
if (error) {
DebugLog(#"Error: %#", error);
}
}
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
self.fetchedResultsController = nil;
}
I am using iOS 5.1.1 with ARC
Have you implemented the NSFetchedResultsController delegates? If you haven't the FRC wont auto-update.
implement the following:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
NSLog(#"POSTDETAIL: controllerWillChangeContent");
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
NSLog(#"POSTDETAIL: controllerDidChangeContent");
[self.tableView reloadData];
}
That should refresh your table view
Related
I'm triing to add NSManagedObject to existing data, but right after i did, my existing data disappears, and i cannot figure out why. Looking the code for like an hour couldnt help much :(
FirstTableViewController.m //DELEGATE points to AppDelegate
- (void)viewDidLoad {
[super viewDidLoad];
self.context=[DELEGATE managedObjectContext];
self.everyWish=[[NSMutableArray alloc]initWithArray:[DELEGATE collectSharedWishes]]; //collects every shared wishes from core data
self.emberek=[[NSMutableArray alloc]initWithArray:[DELEGATE collectUserNames]]; //collects every user
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:IDENTIFIER forIndexPath:indexPath];
User *emberek=[self.emberek objectAtIndex:indexPath.row];
NSMutableArray *currentUserWishes=[[NSMutableArray alloc]initWithArray:[DELEGATE collectCurrentUserSharedWishes:emberek]]; //collects the current user wishes only from core data
int completed=0;
for(SharedWishes *wish in currentUserWishes)
if([wish.wishIsPurchased isEqual:#1])
completed++;
cell.textLabel.text=emberek.userName;
cell.detailTextLabel.text=[NSString stringWithFormat:#"Wishes: %lu, purchased: %d", (unsigned long)currentUserWishes.count, completed];
return cell;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([segue.identifier isEqualToString:#"pushToCurrentUser"]){
User *user=[self.emberek objectAtIndex:[[self.tableView indexPathForSelectedRow]row]];
SharedTableViewController *wishTVC=[segue destinationViewController];
wishTVC.user=user;
NSLog(#"%#", user);
}
}
OtherTableViewController.h
#property NSManagedObjectContext *context;
#property User *user;
OtherTableViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
self.context=[DELEGATE managedObjectContext];
self.format=[[NSDateFormatter alloc]init];
self.todayFormat=[[NSDateFormatter alloc]init];
self.results=[[NSMutableArray alloc]init];
self.wishes=[[NSMutableArray alloc]initWithArray:[DELEGATE collectCurrentUserSharedWishes:self.user]];
self.navigationItem.title=[NSString stringWithFormat:#"%#'s list", self.user.userName];
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(refreshTableViewData) name:#"refreshTableViewData" object:nil];
self.refreshControl=[[UIRefreshControl alloc] init];
[self.refreshControl setTintColor:[UIColor redColor]];
[self.tableView addSubview:self.refreshControl];
[self.refreshControl addTarget:self action:#selector(pullDownToRefresh:) forControlEvents:UIControlEventValueChanged];
}
-(void)fetch:(CKRecord *)obj
{
NSError *error;
SharedWishes *wish=[NSEntityDescription insertNewObjectForEntityForName:#"SharedWishes" inManagedObjectContext:self.context];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"SharedWishes" inManagedObjectContext:self.context];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"wishID==%#", obj.recordID.recordName];
[fetchRequest setPredicate:predicate];
NSArray *fetchedObjects = [self.context executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil)
NSLog(#"Nem sikerult az ab-t elerni!, %#", [error localizedDescription]);
if([fetchedObjects isEqual:#[]])
{
User *currentUser=[[DELEGATE fetchUserWithID:self.user.userID] objectAtIndex:0]; //fetches the current user, which i already have, tried before using [self.user addSharedWishesObject:wish]
wish.wishName=obj[#"name"];
wish.wishPrice=obj[#"price"];
wish.wishIcon=obj[#"icon"];
wish.wishTime=obj[#"creationDate"];
wish.wishLocation=obj[#"location"];
wish.wishDescription=obj[#"description"];
wish.wishType=obj[#"type"];
wish.wishURL=obj[#"url"];
wish.wishImage=obj[#"image"];
wish.wishID=obj.recordID.recordName;
wish.wishIsPurchased=obj[#"purchased"];
wish.wishChangeTag=obj.recordChangeTag;
NSError *error;
[currentUser addSharedWishesObject:wish];
if(![self.context save:&error])
NSLog(#"Nem tudtam eltarolni az adatbazisban, oka: %#", [error localizedDescription]);
}
else
{
wish=[fetchedObjects objectAtIndex:0];
if(![wish.wishChangeTag isEqualToString:obj.recordChangeTag]){
wish.wishName=obj[#"name"];
wish.wishPrice=obj[#"price"];
wish.wishIcon=obj[#"icon"];
wish.wishTime=obj[#"creationDate"];
wish.wishLocation=obj[#"location"];
wish.wishDescription=obj[#"description"];
wish.wishType=obj[#"type"];
wish.wishURL=obj[#"url"];
wish.wishImage=obj[#"image"];
wish.wishID=obj.recordID.recordName;
wish.wishIsPurchased=obj[#"purchased"];
wish.wishChangeTag=obj.recordChangeTag;
//wish.user.userID=obj.creatorUserRecordID.recordName;
NSError *error;
if(![self.context save:&error])
NSLog(#"Nem tudtam eltarolni az adatbazisban, oka: %#", [error localizedDescription]);
}
}
}
Here is the code, it runs without an error, everything declared properly, although i didnt mention (in order to simplify my code).
After running, I have all the users, but only the last one's wishes appears, calling the NSMutableArray *currentUserWishes=[[NSMutableArray alloc]initWithArray:[DELEGATE collectCurrentUserSharedWishes:emberek]]; from FirsTableViewController. The last one has correct amount of objects, the previous ones have #"0 objects".
Saving them right in the OtherTableViewController? Any thoughts?
ps: posting more details if needed
BUG fixed:
Right after I saved the objects, I called a method, in which I compared my elelements (in my DB) to the cloud elements, and delete those which wasn't available any longer in the clouds from my persistent database. A "wrong" predicate caused the main issue because I queried all my database objects, instead of the particular user's elements
I need to implement cancel/save behavior for the Core Data objects. I have a UITableView for which I got data from NSFetchedResultsController.
- (void) configureWithCategoryItem: (CategoryItem *) categoryItem
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:[CategoryItem entityName]];
request.predicate = [NSPredicate predicateWithFormat:#"categoryId = %#", categoryItem.categoryId];
request.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"title" ascending:YES]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:[CategoryItem defaultContext]
sectionNameKeyPath:#"title"
cacheName:nil];
[self.fetchedResultsController performFetch: NULL];
self.layers = [self categoryLayers];
self.sectionTitle = categoryItem.title;
}
Every click on the cell in this table view
LayerItem *layerItem = [self.layers objectAtIndex:indexPath.row];
layerItem.isSelected = [NSNumber numberWithBool:YES];
And in the navigation bar I have two buttons:
Cancel - I need to dismiss controller without saving the results to the core data,
Save - I need to save results and dismiss controller
The save method now looks like this:
- (void) saveChanges
{
NSManagedObjectContext *localContext = self.fetchedResultsController.managedObjectContext;
if ([localContext hasChanges]) {
[localContext save: NULL];
[[NSNotificationCenter defaultCenter] postNotificationName:kEADatabaseWasUpdated object:nil];
}
}
Is there some instrument in Core Data which can help me to implement this behavior?
I tried to use separate NSManagedObjectContext and mergeChangesFromContextDidSaveNotification but didn't receive good result.. Waiting for your help.
- (void) cancelChanges
{
NSManagedObjectContext *localContext = self.fetchedResultsController.managedObjectContext;
[localContext rollback];
}
The problem: I'm getting NSManagedObjectContextDidSaveNotification notifications. I'm merging the changes into my NSFetchedResultsController's context. But the NSFetchedResultsController doesn't fire the didChangeSection, didChangeObject, controllerDidChangeContent methods.
self.managedObjectContext = [NSManagedObjectContext MR_context]; //set up my own context to avoid deadlocking
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"Master"];
self.fetchedResultsController.delegate = self;
[self.managedObjectContext setStalenessInterval:0];
//listen for changes in the main context:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(managedObjectContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:[NSManagedObjectContext MR_defaultContext]];
- (void)managedObjectContextDidSave:(NSNotification *)notification {
NSLog(#"[%# %#] REFRESH!", THIS_FILE, THIS_METHOD); //this happens
void (^mergeChanges) (void) = ^{
for(NSManagedObject *object in [[notification userInfo] objectForKey:NSUpdatedObjectsKey]) {
//this happens (a lot)
[[self.managedObjectContext objectWithID:[object objectID]] willAccessValueForKey:nil];
}
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
};
if ([NSThread isMainThread]) {
mergeChanges();
}
else {
dispatch_sync(dispatch_get_main_queue(), mergeChanges);
}
}
So again, I get the notifications just fine. But my fetched results controller doesn't update. This is killing me! Please help!
EDIT: Also tried this:
self.fetchedResultsController = [NSManagedObject MR_fetchController:fetchRequest delegate:self useFileCache:NO groupedBy:nil inContext:[NSManagedObjectContext MR_defaultContext]];
Which causes the same deadlocking. If I do this:
self.fetchedResultsController = [NSManagedObject MR_fetchController:fetchRequest delegate:self useFileCache:NO groupedBy:nil inContext:[NSManagedObjectContext MR_context]]; //note that I just changed the context
The fetchedResultsController does not update its results.
I don't see how or on which context you are saving. If you are not saving all the way down to the persistent store, then the defaultcontext is not getting those changes and this your nsfetchedresultscontroller won't update either. You may need to set up a connection between your local context and which ever context the nsfrc is using in order for changes to propagate.
I'm working on a CoreData App which displays the content of the CoreData in a TableView.
The Problem is, after adding a string the tableView is not Updating the Content of the CoreData.
i tried it with:
- (void)viewWillAppear:(BOOL)none
{
[self.tableView reloadData];
}
But no Chance, its not reloading.
Any guesses or should I post some more Code? thank you :)
So more Code :)
Saving string to CoreData:
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
AppDelegate *appDelegate =
[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context =
[appDelegate managedObjectContext];
NSManagedObject *newProduct;
newProduct = [NSEntityDescription
insertNewObjectForEntityForName:#"Products"
inManagedObjectContext:context];
[newProduct setValue: _textField.text forKey:#"productName"];
NSError *error;
[context save:&error];
NSLog(#"Product saved");
}
And the Code of the TableViewController:
#implementation CartViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
//Core Data Versuche
/*
Fetch existing events.
Create a fetch request for the Event entity; add a sort descriptor; then execute the fetch.
*/
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"Products"];
[request setFetchBatchSize:20];
// Order the events by creation date, most recent first.
// NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"creationDate" ascending:NO];
// NSArray *sortDescriptors = #[sortDescriptor];
// [request setSortDescriptors:sortDescriptors];
// Execute the fetch.
NSError *error;
AppDelegate *delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
NSArray *fetchResult = [delegate.managedObjectContext executeFetchRequest:request error:&error];
if (fetchResult== nil) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
// Set self's events array to a mutable copy of the fetch results.
[self setCartProductsArray:[fetchResult mutableCopy]];
/*
Reload the table view if the locale changes -- look at APLEventTableViewCell.m to see how the table view cells are redisplayed.
*/
__weak UITableViewController *cell = self;
[[NSNotificationCenter defaultCenter] addObserverForName:NSCurrentLocaleDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[cell.tableView reloadData];
}];
// Ende Core Data Versuche
}
- (void)viewWillAppear:(BOOL)none
{
[self.tableView reloadData];
}
What you probably want to do is use an NSFetchedResultsController.
Make sure that when you are updating the data, you are really just updating CoreData.
When things change in CoreData and you have an NSFetchedResultsController you can subscribe to updates. Apple has great documentation on how to make this work. These updates will update your table correctly using the delegation methods. This ends up mostly being a copy and paste from the documentation.
I have set up a 1 to many relationship on my core data entities. I am trying to show the detailview copy of the associated data. Currently I have the prepareforseague: method working with the original entity(Routines), however I am at a lose as to how to show the linked entity (RoutinesDetails).
FBCDRoutineViewController
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// Fetch the devices from persistent data store
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Routines"];
self.routines = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
[self.tableView reloadData];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"UpdateDevice"]) {
NSManagedObject *selectedDevice = [self.routines objectAtIndex:[[self.tableView indexPathForSelectedRow] row]];
FBCDRoutineViewController *destViewController = segue.destinationViewController;
destViewController.routines = selectedDevice;
}
FBCDRoutineDetailViewController
- (NSManagedObjectContext *)managedObjectContext {
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;
}
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// Fetch the devices from persistent data store
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"RoutinesDetails"];
self.routines = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
}
- (void)viewDidLoad
{
[[self navigationController] setNavigationBarHidden:NO animated:YES];
[super viewDidLoad];
// Do any additional setup after loading the view.
if (self.routines) {
[self.testLabel setText:[self.routines valueForKey:#"routinename"]];
}
}
FBCDRoutineDetailViewController
#property (strong) NSManagedObject *routines;
This is my first time with core data and I am looking at how to show the Details entity. Am I Close to getting it working? If not can I get directed at to what I should be looking at.
Any suggestions?
If I understand your problem correctly, you want to display all RoutinesDetails objects
that are related to the Routines object passed in prepareForSegue.
Then you would declare two properties in the FBCDRoutineDetailViewController:
#property (strong) NSManagedObject *routines; // the passed object
#property (strong) NSManagedObject *routineDetails; // the displayed details objects
and fetch them like this:
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"RoutinesDetails"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"routineinfo == %#", self.routines];
[fetchRequest setPredicate:predicate];
NSError *error;
self.routineDetails = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
self.routineDetails is now the data source array for the details view.
(Remark: For displaying the result set of a Core Data request in a table view,
you might also consider to use a NSFetchedResultsController.)
I think I see several problems--mostly related to the timing of these various calls.
I believe viewDidLoad is called on your detail view before the prepareForSegue is called. So your code in viewDidLoad is trying to display data about your detail item before it has been set.
Then the code in viewDidAppear looks to be overwriting the value you set in prepareForSegue, which doesn't make sense to me (although by this time the view is already displayed and it's not going to affect the label you tried to set in viewDidLoad).
Also, executeFetchRequest: returns an NSArray, not an NSManagedObject, so assigning the result of your fetch to your NSArray property is a BAD idea.