Search NSFetchedResults by changing Settings in another view - ios

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.

Related

How can I reload tableview in viewDidLoad using delegate?

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.

save NSManagedContext in some cases only

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];
}

Core Data and didSelectRowAtIndexPath

i'm trying to learn to use core data in my apps. how ever i'm stuck here at didSelectRowAtIndexPath
i want to be able to use my uitableview as i did before if possible
arrayA is a nsarray filled from a plist. and i can't figure out how to change this code to work with Core Data
in my core data i have HovedMenu BarneDaab and Graviditeten they all got attributes with menuPunkt that has strings in them. so the question is how do i get this code to work with core data ?
i think the arrayA has to change ofc, but to what ?
this is the old code i used when i loaded from plist. i want to upgrade it to core data
if ([arrayA[indexPath.row][#"menuPunkt"]isEqualToString:#"Barnedåb"]) {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"BarneDaab" bundle:nil];
barneDaabMenuViewController *controller = [storyboard instantiateViewControllerWithIdentifier:#"barneDaab"];
[self.navigationController pushViewController:controller animated:YES];
}
Here's my fected :
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSError *error;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
MBBAppDelegate *appDelegate = (MBBAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext* context = appDelegate.managedObjectContext;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Hovedmenu" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"menuPunkt" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:context sectionNameKeyPath:nil
cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
Basically it would seem that all you need to do is to replace arrayA[indexPath.row][#"menuPunkt"] with a call to the FRC to get the appropriate object (just like you do to update the cell contents).
[[self.fetchedResultsController objectAtIndexPath:indexPath] valueForKey:#"menuPunkt"]
(You need to change the reference method for #"menuPunkt" because you used to have dictionaries and now you have managed objects so the syntactic sugar doesn't work).

how to pass objects between view controllers

I have a table view that lists athletes. when an athlete is selected, I wish to have the detail view controller (the controller that is pushed onto the stack) to know all the attributes about the athlete. his/her name, birthday, phone number, etc. But im unsure on how to pass this information.
allathletes.h
-(void)viewWillAppear:(BOOL)animated{
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
_managedObjectContext = [appDelegate managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *athlete = [NSEntityDescription entityForName:#"Athlete" inManagedObjectContext:_managedObjectContext];
[request setEntity:athlete];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"last" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc]initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[_managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil){
//handle error
}
[self setAthleteArray:mutableFetchResults];
[self.tableView reloadData];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
NSString *segueIdentifier = [segue identifier];
if ([segueIdentifier isEqualToString:#"setAthlete"])
{
UINavigationController *navController = (UINavigationController *)[segue destinationViewController];
AllAthletes *athleteList = (AllAthletes *)[[navController viewControllers] lastObject];
//the line below gets an error :(
AthleteDetail.managedObjectContext = self.managedObjectContext;
}
}
Before pushing a detail view controller, set a property on it with the data to be displayed, like:
myDetailViewController.myModel = selectedModel;
In the detail view, you can set up the view using this data in viewWillAppear.
I think you're going to want to use delegates. Here is a great tutorial on how to do that: Link

NSFetchedResultsController won't change on reloadData

I have an iOS app in Master-Detail form where posts made by users are shown in the master view and comments shown in the detail. When I select a post I can see all of the comments just fine but when I go back and select another post, the data does not change to the comments for the next post.
I call this in viewDidLoad
self.fetchedResultsController = nil;
[self.tableView reloadData];
But nothing happens.
Here is my code for fetchedResultsController
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Comment" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"question == %#", self.detailItem];
[fetchRequest setPredicate:predicate];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"creator" ascending:YES];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"Master"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
// 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 in detail %#, %#", error, [error userInfo]);
abort();
}
return _fetchedResultsController;
}
The reason your fetched-results-controller doesn't refresh is because you have set a cache. You will either need to pass nil as your cache-name, or delete the cache prior to refreshing the FRC:
[NSFetchedResultsController deleteCacheWithName:#"Master"]
Have you tried calling your reset code in viewDidAppear instead? viewDidLoad is typically not called multiple times in the view's lifecycle. viewWillAppear/viewDidAppear are called when the view is actually coming on screen.
Have you tried to breakpoint your table reload method to make sure it's actually being called?
Just try this code:
before calling this method don't forget to write self.fetchedResultsController = nil;
- (NSFetchedResultsController *)fetchedResultsController // attaches an NSFetchRequest to this UITableViewController
{
if (_fetchedResultsController != nil )
{
return _fetchedResultsController;
}
NSLog(#"Length:: %d",[[_fetchedResultsController mutableCopy] length]);
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Comment"];
[fetchRequest setIncludesSubentities:YES];
// Sort by Application name
NSSortDescriptor* sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:#"creator" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)];
//Sort by CommandNameloca
NSArray* sortDescriptors = [[NSArray alloc] initWithObjects: sortDescriptor1, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[fetchRequest setFetchBatchSize:100];
ordersArr=[[NSMutableArray alloc]initWithArray:sortDescriptors];
NSLog(#"----- ordersArr Count == %d",[ordersArr count]);
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"creator" cacheName:#"Master"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error])
{
// 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]);
abort();
}
return _fetchedResultsController;
}

Resources