Saving Core Data From Multiple TableVIews [duplicate] - iphone

This question already has answers here:
Querying Core Data with Predicates - iPhone
(4 answers)
Closed 2 years ago.
I have an app that has multiple tableviews and I want to use Core Data to capture all the data. I have two entities - freezers and items. In the first tableview I add a freezer and it saves correctly. I quit the app, re-open, and it is there. I click on the freezer (opening another tableview) and add some items and I can see them in my new sectioned tableview. I quit my app, restart it, see the freezer, click on it and there are no items.
I have my managedObjectContext in my appDelegate and reference it from there using all views, so I am not creating multiple instances. Here is the code I use to save the items to a freezer, both the managedObjectContext and my itemsArray:
Item *item = (Item *)[NSEntityDescription insertNewObjectForEntityForName:#"Item" inManagedObjectContext:[delegate managedObjectContext]];
[item setFreezer:freezerName];
[item setName:name];
[item setQuantity:quantity];
[item setSection:section];
[item setAdded:added];
[item setNotes:notes];
NSError *error = nil;
if (![[delegate managedObjectContext] save:&error]) {
NSLog(#"Freezer info didn't save. Need to handle this.");
}
[items insertObject:item atIndex:0];
Here is the code I use in the ItemViewController to retrieve the items within viewDidLoad:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"freezer == '%#'", freezerName];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Item" inManagedObjectContext:[delegate managedObjectContext]];
NSSortDescriptor *sorts = [[NSSortDescriptor alloc] initWithKey:#"section" ascending:NO];
NSArray *sort = [[NSArray alloc] initWithObjects:sorts, nil];
[request setSortDescriptors:sort];
[request setEntity:entity];
[request setPredicate:predicate];
NSError *error = nil;
NSMutableArray *results = [[[delegate managedObjectContext] executeFetchRequest:request error:&error] mutableCopy];
if(results == nil) {
NSLog(#"Error fetching results... need to handle");
}
[self setItems:results];
NSLog(#"items count:%d", [items count]);
The item count returned is zero.
I am completely stumped and have spent several hours searching online, trying different things, and I can't figure it out. I know there are some much smarter coders out there and I hope one of you can see what the problem is.

I would try to change the predicate to
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"freezer like %#", freezerName];
Hope that helps!

Maybe the problem is that you load data in
- viewDidLoad:
method. It's called only once when your view is loaded, so when underlaying data get's changed, your view controller is not aware about it.
You can either move your loading code to
- viewWillAppear:
method or introduce notifications to spread the information that data store has changed its state and reload table views upon that event.
The best way is probably to use NSFetchedResultsController as your data source, as its always aware of its data store changes. Check docs for reference to that class.

Related

Dynamically setting fetchLimit for NSFetchedResultsController

I'm using am NSFetchedResultsController to populate data onto a UITableView.
It's a simple chat app and I want to load the latest 25 messages onto the table first and load more as the user scrolls up to see older messages (the chat message are in a ascending order).
I call a method that will setFetchLimit: for the NSFetchedResultsController in the willDisplayCell: like so....
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if(indexPath.row == 0)
{
[self performSelector:#selector(getMoreMessages) withObject:nil afterDelay:1.0];
}
}
when the first row of the UITableView has been displayed, getMoreMessages will try to reset the fetchLimit reload the UITableView like so.....
- (void)getMoreMessages
{
maxListItems += 25;
NSLog(#"set maxListItems: %d", maxListItems);
[self.resultsController.fetchRequest setFetchLimit:maxListItems];
[self._tableView reloadData];
}
However, it doesn't seem to be working, the table data will not change.
The initial NSFetchRequest is set like so...
NSFetchRequest *chatDataRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"ChatData" inManagedObjectContext:appDelegate.managedObjectContext];
[chatDataRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(key != 0 OR messageNo != 0) and matchNo = %d", matchNo];
[chatDataRequest setPredicate:predicate];
NSSortDescriptor *sortDescripter1 = [[NSSortDescriptor alloc] initWithKey:#"status" ascending:YES];
NSSortDescriptor *sortDescripter2 = [[NSSortDescriptor alloc] initWithKey:#"messageNo" ascending:YES];
NSArray *sortDescripters = [[NSArray alloc] initWithObjects:sortDescripter1, sortDescripter2, nil];
[chatDataRequest setSortDescriptors:sortDescripters];
[sortDescripters release];
[sortDescripter1 release];
[sortDescripter2 release];
[chatDataRequest setFetchLimit:25];
NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:chatDataRequest managedObjectContext:appDelegate.managedObjectContext sectionNameKeyPath:nil cacheName:[NSString stringWithFormat:#"%d_chat.cache", matchNumber]];
[chatDataRequest release];
fetchedResultsController.delegate = self;
NSError *error;
BOOL success = [fetchedResultsController performFetch:&error];
if(!success) NSLog(#"error: %#", error);
self.resultsController = fetchedResultsController;
And back to the question.
How can one dynamically change the fetchLimit for an NSFetchedResultsController?
Any hits would be awesome!
Thanks!
Instand using setFetchLimit, using setBatchSize, see below for detail answer.
The count of the fetchedObjects array might not what you want to do, since it does not update the changes from the persistent store. From NSFetchedResultsController documentation:
The results array only includes instances of the entity specified by the fetch request (fetchRequest) and that match its predicate. (If the fetch request has no predicate, then the results array includes all instances of the entity specified by the fetch request.)
The results array reflects the in-memory state of managed objects in the controller’s managed object context, not their state in the persistent store. The returned array does not, however, update as managed objects are inserted, modified, or deleted.
If you only want to fetch 20 objects, set the fetch limit of the NSFetchRequest. If you want only to keep 20 objects in memory, use setBatchSize of the NSFetchRequest object.
figured this one out.
looks like I have to run performFetch: after I change the fetchLimit. :D
[self.resultsController.fetchRequest setFetchLimit:maxListItems];
[self.resultsController performFetch:&error];

Query interface for iPhone CoreData store

another iPhone newbie question...
I have the following:
NSPersistentStoreCoordinator
NSManagedObjectContext
NSManagedObjectModel
Is it possible to run queries directly on the store (since its a sqlite DB)? I'm trying to delete all the records from a tableview, and figured a "DELETE FROM table" would be nice and quick as opposed to looping through the records and removing them manually (which i'm also struggling with).
Thanks for your time,
James
Core data acts as a wrapper for the underlying data store, so it's not really a great idea to begin circumventing core data. Additionally, core data adds additional information to your DB, so directly accessing the DB may (or may in the future) cause problems.
To delete all records via core data, I have the following:
+ (void) deleteAll {
NSManagedObjectContext *managedObjectContext = [(myAppDelegate*)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:[[self class] description] inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSError *error;
NSArray *items = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
for (NSManagedObject *managedObject in items) {
[managedObjectContext deleteObject:managedObject];
NSLog(#"%# object deleted",[[self class] description]);
}
}

How do I get Attributes from Core Data into an Array for - iPhone SDK

I'm trying to retrieve data from Core Data and put it into a Mutable Array
I have an Entity called 'Stock' and in Properties, attributes called : code, price & description...
How do I get the data stored in these attributes into a simple Mutable Array?
I've added this code...
NSMutableArray *array = [[NSMutableArray alloc]init];
[array addObject:[stock valueForKey:#"code"]];
and I get this error...
'-[NSCFArray insertObject:atIndex:]: attempt to insert nil'
I have a 'Managed Object Class' called 'Stock' and declared called stock. Am I missing something?
If I do this in the -cellForRowAtIndexPath...
Stock *stock1 = [fetchedResultsController objectAtIndexPath:indexPath];
array = [[NSMutableArray alloc] init];
[array addObject:stock1.code];
NSLog(#"Filtered List is? %#", array);
In the console I can see these 2 items
'The Filtered array is 810005'
'The Filtered array is 810007
'
What must I do to get these items(810005 & 810007) into an array set up in the -viewDidLoad method? Like it does in the -cellForRowAtIndexPath?
Update
Hi Marcus,
Finally got it working (well, 80%)
I put this in the -cellForRowAtIndexPath
Stock *product = nil;
if (tableView == self.searchDisplayController.searchResultsTableView)
{
filteredListContent = [NSMutableArray arrayWithObjects:stock1.code, nil];
product = [self.filteredListContent objectAtIndex:indexPath.row];
[self configureFilteredCell:cell atIndexPath:indexPath];
[filteredListContent objectAtIndex:indexPath.row];
NSLog(#"Filtered List Array List is? %#", stock1.code);
}
else
{
listContent = [NSMutableArray arrayWithObjects:stock1.code, nil];
[self configureCell:cell atIndexPath:indexPath];
NSLog(#"List Array List is? %#", stock1.code);
}
Then I used this code in the scope
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
self.savedSearchTerm = searchText;
if (searchText !=nil)
{
NSPredicate *predicate =[NSPredicate predicateWithFormat:#"code beginsWith[cd] %#", searchText];
[fetchedResultsController.fetchRequest setPredicate:predicate];
}
else
{
NSPredicate *predicate =[NSPredicate predicateWithFormat:#"code contains[cd] %#", searchText];
[fetchedResultsController.fetchRequest setPredicate:predicate];
[self.tableView reloadData];
}
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error])
{
// Handle error
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
[self.tableView reloadData];
Everything is filtering fine but when I hit cancel on the search, it's not reloading the original data...
I won't be defeated...!!
Thanx
Since you are having this issue in your -viewDidLoad, I am guessing (and without the code from -viewDidLoad, it is only a guess) that you are trying to fetch objects from the NSFetchedResultsController before the -executeFetch: has been called on the controller and therefore you are in the land of nils.
I would suggest setting a break point in your -viewDidLoad and watching the values and you walk through your code. This will tell you what is nil and where.
Of course a better question is, why are you trying to put NSManagedObject instances into a NSMutableArray? Since they are already in your NSFetchedResultsController is there really a need to build up another array? What is the end goal?
Update
Now I understand what you are trying to do.
Solution 1
Only populate the array when a search has been conducted. Take a look at the http://developer.apple.com/iphone/library/samplecode/TableSearch/index.html example code and you should see how to apply it to your situation.
If you want to enter the table view with a pre-defined search then you need to perform it after you have executed a -performFetch: in the NSFetchedResultsController.
Solution 2
Modify the NSPredicate on the NSFetchedResultsController to include your search terms and then execute -performFetch: on the NSFetchedResultsController, you may have to do a -reloadData on the table as well, I am not sure.
When the user clears the search field you reset the predicate and re-fetch everything. Since it is all cached there should be no performance penalty.
Solution 2 just occurred to me and I have not tested it personally but there is no reason it shouldn't work just fine. Should even give you live updates within the search.
Have you read the documentation? You fetch your Stock instances (all of them or filter them with a predicate), then do with them whatever you please.
You can then add their properties to an array individually:
[array addObject:[stockInstance valueForKey:#"price"];
... or use a combination of < NSKeyValueCoding > protocol methods such as -dictionaryWithValuesForKeys: NSDictionary methods such as -objectsForKeys:notFoundMarker: to get an array for given keys.
This may or may not actually be what you need to do, though. It depends on what you intend to use the resulting array for. If you want a quick sum of all matching Stock instances' "price" values, for example, you can use Set and Array Operators. It really depends on what you're trying to achieve.
When I got your error,
'-[NSCFArray insertObject:atIndex:]: attempt to insert nil'
I had given the fetchedRequest a sort descriptor that had a nil key. The error appeared when I used these lines:
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:nil ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
The error disappeared when I set the key to #"name":
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];

iPhone How to modify the data contained on the Persistent store using Core Data

I'm stuck trying to figure out how to modify the data contained on the persistent store.
I'm writing an application with several views using a UITabBarController, my core data methods are located mainly on the main application delegate but I will only be using this data from the UItableViewController view.
In order to use the managedObjectContext created in the main application delegate from the UITableViewController I use the following on the viewDidLoad: method:
MessageAppDelegate *appDelegate = (MessageAppDelegate *)[[UIApplication sharedApplication] delegate];
managedObjectContext = [appDelegate managedObjectContext];
The application then displays some messages in the table and when a user selects a UITableViewCell (didSelectRowAtIndexPath) I get the ID of the message object and call the following method:
[self readMessage:pk];
-(void)readMessage:(NSInteger)pk {
// First I select the data
NSFetchRequest *request = [[NSFetchRequest alloc] init];
// had to setReturnsObjectsAsFaults to NO so I could access the message data
[request setReturnsObjectsAsFaults:NO];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Message" inManagedObjectContext:self.managedObjectContext];
[request setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"pk == %d", pk];
[request setPredicate:predicate];
NSError *error;
NSArray *items = [self.managedObjectContext executeFetchRequest:request error:&error];
[request release];
// Then I update the object
for (Message *thisMessage in items) {
//I display the message to the console before updating to check the value
DLog(#"before reading message %#", thisMessage);
// we set the message flat to YES
[thisMessage setRead:YES];
// we set some sample text here (just for testing)
[thisMessage setMessageText:#"New message text"];
// I then display the message to the console checking that the flag and text has been updated
DLog(#"read message %#", thisMessage);
}
// Finally I save the updated message calling the function posted below
[self saveMOC];
}
- (void)saveMOC {
NSError *error;
if (![managedObjectContext save:&error]) {
NSLog(#"there was an error saving the message!");
}
}
After that the data gets updated correctly and if I fetch the data from the managedObjectContext after saving it I get the correct values.
I verified this by adding the following code to at the end of readMessage method:
request = [[NSFetchRequest alloc] init];
//required to avoid presenting objects as faults!!
[request setReturnsObjectsAsFaults:NO];
entity = [NSEntityDescription entityForName:#"Message" inManagedObjectContext:[self managedObjectContext]];
[request setEntity:entity];
//Set the sort descriptor
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"pk" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];
//Execute the request
NSMutableArray *mutableFetchResults = [[self.managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error later
DLog(#"ERROR: Unable to fetch the results");
}
[self setMessagesArray:mutableFetchResults];
NSLog(#"Data now is: %#", mutableFetchResults);
[mutableFetchResults release];
[request release];
The problem is that if I exit from the application and launch it again all my messages lose the read property (or any other changes I make) and the tableview loads the data as it was first saved onto the persistent store.
Try this and see if the object changes are actually being saved
- (void)saveMOC {
NSError *error;
if (![managedObjectContext save:&error]) {
NSLog(#"there was an error saving the message!");
} else {
NSLog(#"The message was saved!");
}
}
So for each call of saveMOC which is successful, you should see a console message. If it is being called and you're seeing the messages, then you must not be altering the 'read message' property. You could check this by inspecting the value of the 'read message' property before and after setting it either using a breakpoint or by using NSLog messages to print its value
Is -readMessage: method defined in your app delegate or in your view controller? My guess is that you're changing properties of an object in different managed object context than one where you try to save changes (MOC in your app delegate), which actually doesn't have an idea that something has changed. On the other hand, MOC which keeps your changes is never saved (changes are kept only in memory) and that for your changes are lost after you restart your app.
Can this be the situation?

Iphone Core data question

I have set up the application with 2 entity, called Category1 and category2. There is a to-many relationship between category1 and category2. When a cell is pushed in tableview (category1), a new tableview will display all category2 cells related to category1. Here is what an example:
I have two category1 items in the first tableview, called Food and Snacks. Item "Food" have a subcategory (category 2) that contains 5 different kinds of food. Item "Snacks" have a subcateogry (category2) that contains 10 different snacks.
So when i push the food item (category1) I just want the food item to be loaded (5 of them). Right now, I can see all 5 of the food item in the tableView, plus the 10 items from the "snacks" category2.
I use this code in category2:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Folder" inManagedObjectContext: tag.managedObjectContext];
[request setEntity:entity];
//NSMutableSet *filtered = [tag mutableSetValueForKey:#"folders"];
// Order the events by creation date, most recent first.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"creationDate" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
[sortDescriptors release];
// Execute the fetch -- create a mutable copy of the result.
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[tag.managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil)
{
// Handle the error.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
// Set self's events array to the mutable array, then clean up.
[self setTagsArray:mutableFetchResults];
[mutableFetchResults release];
[request release];
need help !
thanks in advance!
First a bit of advice: In your root table view you should be using a NSFetchedRequestController to manage the top level objects. Take a look at the Recipes application in Apple's sample code for a demonstration.
As for the primary issue you are seeing, what you should do is push the category1 object to the child view controller. The child view controller can then query the that category1 object for all of its children to display. No fetch is required on the child view controller because you already have the parent object. Again, the recipe example app from Apple will demonstrate this quite clearly for you.
Do it in views, so there are two views for the subcategories, one for food, one for snacks. Then on the first table view have it set so when you tap on food, it'll go to the food view, same with the snacks. If you can't get it working try with UIButtons. What I do is if all else fails, go to UIButtons :) If you need help getting it to switch views I made an example as part of my 25 apps in august project, you can find my example here: http://appeveryday.wordpress.com/2009/08/02/app-3/