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];
}
Related
I have simply TODO list in which there are:
protocol's method reloadTableViewWhenItChanges;
fetch data (i.e. core data);
Table view reloads after stop/run compiler.
But I need to reload table view in viewDidLoad (not in viewWillAppear/viewDidAppear), using delegation table reloads only after something has changed in managedObjectContext.
How can I do that?
Here is my code (what is wrong in it? Where do I have to put [self.tableView reloadData] in my code, for example?):
- (void)viewDidLoad {
[super viewDidLoad];
self.title = #"Note List";
self.barButtonAddNote = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self
action:#selector(addNewNote:)];
[self.navigationItem setRightBarButtonItem:self.barButtonAddNote animated:YES];
[self reloadTableViewWhenItChanges]; }
- (void)reloadTableViewWhenItChanges // protocol's method {
self.addNoteViewController.delegate = self;
if ([self.managedObjectContext hasChanges])
{
NSError *error;
[self.managedObjectContext save:&error];
}
[self fetchData];
[self.tableView reloadData]; }
- (void)fetchData {
NSFetchRequest *request = [[NSFetchRequest alloc]init];
NSEntityDescription *note = [NSEntityDescription entityForName:#"Note"
inManagedObjectContext:self.managedObjectContext];
[request setEntity:note];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"task" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
NSError *error = nil;
NSArray *mutableFetchResults = [self.managedObjectContext executeFetchRequest:request error:&error];
if (mutableFetchResults != nil){
self.notesArray = [[NSMutableArray alloc] initWithArray:mutableFetchResults];
} }
As mentioned by #eik_cloner, you don't need to reload table view in viewDidLoad because it's done automatically.
If you want to do it manually then you have to call reloadData method of UITableView wherever you want as:
[yourTableView reloadData];
In Objective C :-
[tableViewName reloadData];
In Swift :-
tableViewName.reloadData()
Make sure you have Configure the delegates with UIViewController through UIStoryboard or By code.
I have read a lot of content and watched several tutorials on how to search Core Data by using a search bar however I have not yet seen anything for how to change a the sort descriptor by passing a setting from a Settings view to the Table View.
I have a search bar button item that when pressed goes to a SearchSettingsVC. The views communicate by passing boolean's from one VC to the other and vise-versa. The problem is that the table is not corresponding accordingly by changing the TableVC order - (I have tried calling self.tableview beginUpdates, self.tableview reload, self fetchedResultsController among other things).
The point is to reorder the TableVC results, not to present only specific results like a predicate does
I have created a delegate for the SettingsVC so that I can pass boolean value to the SettingsVC which is then capable of returning a different changed value if there are any changes.
The problem is that I cannot manage to get the table view (or prehaps even the fetched results) to update.
Here is my code for my -(NSFetchedResultsController*) fetchedResultsController method:
// return if already initialized
if (self.fetchedResultsController != nil) {
return self.fetchedResultsController;
}
if (dateSearch == true){
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
// the entity to fetch
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Details" inManagedObjectContext:managedObjectContext];
// how to sort the data
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"addDate" ascending:YES];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
[request setSortDescriptors:[NSArray arrayWithObject:sort]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
// fetch the data
NSError *e = nil;
if (![self.fetchedResultsController performFetch:&e]) {
NSLog(#"fetch error(Date): %#", e );
abort();
}
NSLog(#"Dates loaded");
}
if (mostAmount == true){
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
// the entity to fetch
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Details" inManagedObjectContext:managedObjectContext];
// how to sort the data
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"amount" ascending:NO];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
[request setSortDescriptors:[NSArray arrayWithObject:sort]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
// fetch the data
NSError *e = nil;
if (![self.fetchedResultsController performFetch:&e]) {
NSLog(#"fetch error (Most Fuel): %#", e);
abort();
}
NSLog(#"Amount loaded");
}
else{
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
// the entity to fetch
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Details" inManagedObjectContext:managedObjectContext];
// how to sort the data
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"addDate" ascending:YES];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
[request setSortDescriptors:[NSArray arrayWithObject:sort]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
// fetch the data
NSError *e = nil;
if (![self.fetchedResultsController performFetch:&e]) {
NSLog(#"fetch error(Date): %#", e );
abort();
}
NSLog(#"Defualt loaded");
}
return self.fetchedResultsController;
I required a default because at the beginning when I initialise the booleans in the TableVC to false they can be updated when I go to my settings VC.
My SearchSettingsVC has UISwitches that change the values from true to false (and vice-versa) which successfully update the equivalent booleans in the TableVC as represented when I return to the SearchSettingsVC.
My prepareForSegue code in my TableVC
if ([[segue identifier] isEqualToString:#"searchSettings"]){
//pass new search settings in here
SearchSelectionSettings * settingsVC = (SearchSelectionSettings *)segue.destinationViewController;
settingsVC.delegate = self;
settingsVC.dateSearch = dateSearch;
settingsVC.mostAmount = mostAmount;
This is my closeSettings method which is located in my TableVC
#pragma mark - SettingsViewControllerDelegate methods
//record the settings changed in the settings view
//dismissViewController changes the screen
- (void)closeSettings:(id)sender {
NSLog(#"Working?");
dateSearch = ((SearchSelectionSettings *)sender).dateSearch;
mostAmount = ((SearchSelectionSettings *)sender).mostAmount;
[self dismissViewControllerAnimated:YES completion:nil];
[self FetchedResultsController];
NSIndexPath * indexPath;
[self tableView:self.tableView cellForRowAtIndexPath:indexPath];
}
Passing boolean values from one VC to other works perfectly. Getting the TableView to update according to the fetchedResults if statements does not, my switches in the SettingsVC are updated every time without any issues. Can anyone help or recommend a tutorial?
The opening lines of the fetchedResultscontroller getter, i.e.:
// return if already initialized
if (self.fetchedResultsController != nil) {
return self.fetchedResultsController;
}
mean that, once your fetchedResultsController has been created, the remaining code will not be executed when you access it. A quick way to resolve your problem would therefore be to set self.fetchedResultsController to nil in your closeSettings method, and then reload your table view. When the tableview reloads, it will access the fetchedResultsController again, and since it is now nil, the above code will be bypassed and your code will be used.
#pragma mark - SettingsViewControllerDelegate methods
//record the settings changed in the settings view
//dismissViewController changes the screen
- (void)closeSettings:(id)sender {
NSLog(#"Working?");
dateSearch = ((SearchSelectionSettings *)sender).dateSearch;
mostAmount = ((SearchSelectionSettings *)sender).mostAmount;
[self dismissViewControllerAnimated:YES completion:nil];
self.fetchedResultsController = nil;
[self.tableView reloadData];
}
Alternatively, you can modify the fetchedResultsController's fetch and then get it to reperform the fetch:
#pragma mark - SettingsViewControllerDelegate methods
//record the settings changed in the settings view
//dismissViewController changes the screen
- (void)closeSettings:(id)sender {
NSLog(#"Working?");
dateSearch = ((SearchSelectionSettings *)sender).dateSearch;
mostAmount = ((SearchSelectionSettings *)sender).mostAmount;
[self dismissViewControllerAnimated:YES completion:nil];
NSFetchRequest *request = [[NSFetchRequest fetchRequestWithEntityName:#"Details"];
NSSortDescriptor *sort;
if (dateSearch == true){
sort = [NSSortDescriptor sortDescriptorWithKey:#"addDate" ascending:YES];
NSLog(#"Dates loaded");
} else if (mostAmount == true) {
sort = [NSSortDescriptor sortDescriptorWithKey:#"amount" ascending:NO];
NSLog(#"Amount loaded");
} else {
sort = [NSSortDescriptor sortDescriptorWithKey:#"addDate" ascending:YES];
NSLog(#"Default loaded");
}
[request setSortDescriptors:[NSArray arrayWithObject:sort]];
self.fetchedResultsController.fetchRequest = request;
// fetch the data
NSError *e = nil;
if (![self.fetchedResultsController performFetch:&e]) {
NSLog(#"fetch error (Most Fuel): %#", e);
abort();
}
[self.tableView reloadData];
}
That way, you can simplify your fetchedResultsController code so it just loads the default.
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.
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