fetching results twice returns nil - ios

Assume I have 3 files in my project:
data model file, dealing with Core Data and fetching info
viewController 1
viewController 2
In the model file I get results us follows:
- (NSArray *) getColonyData
{
NSManagedObjectContext *cxt = [self managedObjectContext];
NSEntityDescription *colonyDesc = [NSEntityDescription entityForName:#"Colony" inManagedObjectContext:cxt];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:colonyDesc];
NSError *error;
NSArray *colonyResults = [cxt executeFetchRequest:request error:&error];
return colonyResults;
}
I run this part of code in viewDidLoad section of the 1st viewController and I get proper results:
NSArray *colonyResults = [model getColonyData];
if (colonyResults != nil)
{
colonyName.text = [[colonyResults objectAtIndex:0] valueForKey:#"name"];
}
else
{
colonyName.text = #"nothing setup yet";
}
Then I move via a segue to a 2nd viewController, when I execute exactly same code (of course updating different UI elements). But this time the result is nil. What am I doing wrong here? Should I release results manually first? No other errors appear.
Thanks.

As suggested in comments:
experimentModel *model; doesn't assign anything; it just declares that a variable exists. Somewhere, you must be setting model to an actual object for the first controller (and probably not doing that for the second one).
Sending messages to nil objects is a common way to not get the results one was expecting. :)

Related

Updating object value in core data is also creating a new object

I have check the following Q&A and while the solutions are much the same the issue remains for me remains:
Saving an updated Core Data instance
Update/Edit coreData managed object
How to update existing object in Core Data?
I have a log in/log out feature in my app.
The Core Data entity "NewLog" has the following attributes
String loginTime
String logoutTime
String loginStatus
String name
When I login it creates a new object in the core data. When I log out I have the object update.The issue is when I logout it updates the existing object but creates a new object also.
Here is my "save" code from my super class so that I don't have to continuously rewrite it and risk errors in my classes:
- (void) saveAndDismiss{
NSError * error = nil;
if ([self.managedObjectContext hasChanges]){
if (![self.managedObjectContext save: &error]){
NSLog(#"Save Failed: %#", [error localizedDescription]);
}
else {
NSLog(#"Save Successful");
}
}
[self dismissViewControllerAnimated:YES completion:nil];
}
In my loginViewController implementation file to update the "loginStatus" and "logoutTime":
- (IBAction)logOutButton:(id)sender {
//access the managed object context and create a shortcut name
NSManagedObjectContext *context = [self managedObjectContext];
//refer to the entity in the core data
NSEntityDescription *entity = [NSEntityDescription entityForName: #"NewLog" inManagedObjectContext:context];
//create a request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity: entity];
// Results should be in descending order of loginTime.
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"loginTime" ascending:NO];
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
//create an array of results
NSArray *results = [context executeFetchRequest:request error:NULL];
NewLog *latestEntity = [results objectAtIndex:0];
//refer to the specific value that I wish to change
NSString * statusOfLogin = latestEntity.loginStatus;
//if the loginStatus in my app is currently YES
if ([statusOfLogin isEqual:#"YES"]){
//create a date formatter
NSDateFormatter *dateformatter=[[NSDateFormatter alloc]init];
//format as such
[dateformatter setDateFormat:#"dd MMM yyyy , HH:mm:ss"];
//convert to a string
NSString *dateInStringFormated=[dateformatter stringFromDate:[NSDate date]];
//hide logout button and display login button
_logOutButton.alpha = 0.0;
_loginButtonStatus.alpha = 1.0;
//status declared as String in .h file
status = #"NO";
//update the object found in "latestEntity"
latestEntity.logoutTime = dateInStringFormated;
//set the login status to be NO
latestEntity.loginStatus = status;
//BOOL declared in the .h file
logStatus = false;
self.loginLabel.text = #"Logged Out";
self.dateLabel.text = dateInStringFormated;
[super saveAndDismiss];
}
}
When I save the updated data it creates a new object containing only the logoutTime and loginStatus.
Is there a solution to stop the new object being created?
Thanks
EDIT
I know that it is creating a new object the following ways:
1. The SQL file displays the new object with the content stated above - logoutTime and loginStatus
2. The tableview displays the new object and in the detail view for it displays the two items.
Checking the area it's coming from is my segue:
//prepare for segue
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
//first check whether the segue is equal to our transition name
if([[segue identifier]isEqualToString:#"loginSegue"])
{
//tell the segue where to go
UINavigationController *navigationController = segue.destinationViewController;
//create reference to view controller
LoginViewController *loginVC= (LoginViewController*) navigationController.topViewController;
//create the new object
NewLog *addLogDetails = [NSEntityDescription insertNewObjectForEntityForName:#"NewLog" inManagedObjectContext: [self managedObjectContext]];
loginVC.logEntry = addLogDetails;
}
}
I know why it's creating a new object but how do I prevent this from happening while remaining able to save?
Nothing in the code in your question is creating a new managed object, so either the problem is elsewhere in your code or you're mistaken that an extra object is being created - you don't say how you have come to this conclusion.
To identify the problem, implement awakeFromInsert on your NewLog object and add a breakpoint. This will be hit every time one of these objects is added, you can then examine the stack trace to find out where the object is being added from.
UPDATE
OK, having done that, you see that you're making a new entity every time the segue is performed. Don't do that. You need to perform a fetch to find the existing login object, and only if there isn't one, or the existing one isn't suitable, create a new one.
If you do a fetch and there's nothing in the array, objectAtIndex:0 will crash. Use firstObject instead and check if the result of that is nil.

retrieve the last saved attribute in a core data database

I'm completely new to core data programming! I'm trying to write an app that is, in short, like this:
The user has a lot of data in a view, these data are the results os lots of calculations done before. in this particular "all results" view, there is an option for saving the data for future reference, to do this the user is asked to enter an identifier (in a textfield) and press the save button.
To retrieve the saved data, the user re-enter the identifier and press the load button.
Almost everything works very well: the data is being saved and loaded... But the problem is that when entering the identifier and pressing the LOAD button, the app loads the first saved data, not the last one! I want the app to load the last saved data when pressing the load button... how can I do that?
I'm very sorry to bother you with this newbie question, but this is the very first time I'm trying to deal with Core Data, and I really want to know how to do this! I thank very much everyone who can help me!
The code for the "LOAD" button is this:
- (IBAction)loadData:(id)sender
{
[_identificador resignFirstResponder];
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entityDesc = [NSEntityDescription entityForName:#"English"
inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDesc];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"(identificador = %#)", _identificador.text];
[request setPredicate:pred];
NSManagedObject *matches = nil;
NSError *error;
NSArray *objects = [context executeFetchRequest:request
error:&error];
if ([objects count] == 0)
{
_status.text = #"No matches";
}
else
{
matches = objects[0];
_dataAtualEng.text = [matches valueForKey:#"dataRCQ"];
_rcqEng.text = [matches valueForKey:#"rcq"];
_status.text = [NSString stringWithFormat: #"%lu matches found", (unsigned long)[objects count]];
}
}
The objects array contains all the objects with the identifier you have specified in the predicate.
In your code you are using
matches = objects[0];
which is giving you the first saved object.
Instead you should be using
matches = objects.lastObject;
However you might want to re-think your implementation as the way to uniquely identify an entry is through its primary key (identificador in your case), which should be different for each save.

How do you pass core data corresponding to a row selected in a table?

I am learning how to use Core Data. I have an app that fills out all the variable of an entity labeled User. I then take these users and load them to a table. At this point the users can be selected and pushed to a new view controller at which point I generate a PDF file of the user selected. So I think I am misunderstanding what it is I have to pass to the view controller for it to access the core data selected in the table. Here is what I have in my table view controller.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.spinner startAnimating];
ReportPDFViewController *reviewViewController = [[ReportPDFViewController alloc] init];
reviewViewController.userInfo = [[self.fetchedResultsController fetchedObjects] objectAtIndex:indexPath.row];
[self.navigationController pushViewController:reviewViewController animated:YES];
}
Then the next view states this
- (void)viewDidLoad
{
[super viewDidLoad];
UIBarButtonItem *barButton = [[UIBarButtonItem alloc] initWithTitle:#"Email PDF"
style:UIBarButtonItemStylePlain
target:self
action:#selector(emailPDF)];
self.navigationItem.rightBarButtonItem = barButton;
TrafficSignalProAppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
context = [appDelegate managedObjectContext];
// Do any additional setup after loading the view.
NSEntityDescription *entitydesc = [NSEntityDescription entityForName:#"User" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entitydesc];
NSError *error;
NSArray *matchingData = [userInfo executeFetchRequest:request error:&error];
NSString *Intersection;
NSString *currentDay;
for (NSManagedObject *obj in matchingData) {
Intersection = sendIntersection;
currentDay = sendDate;
}
NSString* fileName = [self getPDFFileName];
[PDFRenderer drawPDF:fileName intersectionSearch:Intersection dateSearch:currentDay];
[self showPDFFile];
self.navigationItem.title = #"Report";
}
So I'm trying to pass the NSManagedObjectContext of the selected row to then load. I am really lost after that. I'm not sure if passing the managed object context is right and if it is I don't know what is wrong with the code in the ReportPDFViewController. I have looked through all the tutorials I can find. I have a limited programming background so any help would be greatly appreciated.
reviewViewController.userInfo = [[self.fetchedResultsController fetchedObjects] objectAtIndex:indexPath.row];
This sets userInfo to an object of type NSManagedObject (or a subclass).
NSArray *matchingData = [userInfo executeFetchRequest:request error:&error];
This is using userInfo as if it's a NSManagedObjectContext. I would imagine you get an invalid selector error here.
What is the actual type of the userInfo attribute? It should be NSManagedObject.
You do not need to do a fetch request in your viewDidLoad. Core Data is not a database. You do not always need to do a fetch request every time you want some information. Once you already have a managed object, you can get information related to it without a fetch request. If you've set up a custom class for it, you can treat it almost like it's an regular objective-C object.
for (NSManagedObject *obj in matchingData) {
Intersection = sendIntersection;
currentDay = sendDate;
}
This code just doesn't make sense. You're looping, but each time through you're assigning the same value to the variables. I don't know what that value is, since sendIntersection and sendDate are not referred to anywhere else in the code you posted. In any case you're not using the results of the fetch request at all.
I'm going to make a wild guess at what you need to do:
Intersection = [userInfo valueForKey:#"intersection"];
currentDay = [userInfo valueForKey:#"date"];
It's a total guess, because I don't know what your data model is. No loop is needed, since you only want and have one user object.

Core Data: import a tree structure with find or insert / duplicate entries

I have a list of Places from a rails app that I'm trying to import in an iOS5 app. Each Place has a parent which is a Place itself.
I'm trying to import that JSON data with Core Data using a dictionary
- (void)initWithDictionary:(NSDictionary *)dictionary {
self.placeId = [dictionary valueForKey:#"id"];
id parent = [dictionary objectForKey:#"parent"];
if (parent && parent != [NSNull null]) {
NSDictionary *parentDictionary = parent;
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"placeId = %#", [parentDictionary objectForKey:#"id"]];
NSArray *matching = fetchedWithPredicate(#"Place", self.managedObjectContext, predicate, nil);
if ([matching count] > 0) {
self.parent = [matching objectAtIndex:0];
} else {
self.parent = [NSEntityDescription insertNewObjectForEntityForName:#"Place" inManagedObjectContext:self.managedObjectContext];
[self.parent initWithDictionary:parentDictionary];
}
}
}
fetchedWithPredicate is a method defined as such
NSArray* fetchedWithPredicate(NSString *entityName, NSManagedObjectContext *context, NSPredicate *predicate, NSError **error) {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setIncludesPendingChanges:YES];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:context];
[request setEntity:entity];
[request setPredicate:predicate];
NSArray *result = [context executeFetchRequest:request error:error];
return result;
}
I also have a validation method in Place.m to make sure I don't create to place with the same placeId (placeId is the id on the server side).
- (BOOL)validatePlaceId:(id *)value error:(NSError **)error {
if (*value == nil)
return YES;
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"placeId = %# AND (NOT self IN %#)", *value, [NSArray arrayWithObject:self]];
NSArray *matching = fetchedWithPredicate(#"Place", self.managedObjectContext, predicate, error);
if ([matching count] > 0) {
return NO;
}
else {
return YES;
}
}
To import the data, I fetch all places from the server, returned in JSON format.
Each Place has its own information, plus a child node with informations about the parent, which means that each parent of multiple children will appear multiple times. It looks like
{ "id": 73,
"name": "Some place",
"parent": { "id": 2,
"name": "Parent's name"}
}
I thought the above code which does kind of a "find or create", with a fetch including unsaved changes, would be alright.
But it still attempt to create multiple entries for some places (and fails to since there's a validation in place). Looking deeper, it indeed insert different core data objects for the same placeId (different pointers), but I don't know why.
Thanks
It sounds like you already have a unique index on id (which is good obviously). I think it is that you are not saving the newly inserted creations to core data prior to expecting it to be returned via fetch. The simple (if perhaps not too performant depending on having lots of rows) would be to add a saveContext call right after each object is inserted/inited.
Another way would be to do it in two passes, first entirely in memory where you create a separate dictionary where the key is the id, and the object is the value. That way you'd be able to ensure each id was only in there once. After they're all in that dictionary, you can then easily (or easier, perhaps) add them all to Core Data.
So, after a bit more investigation, it's due to the fact that I was sorting my data by name...
So if a place A had 5 children, and 3 of them had a name that was before A's name, the code would:
create those 3 children with a parent that don't have any parent itself (because my json doesn't return infos about the parent's parent)
create A
create the 2 other children with A as parent (probably because of the way it's sorted, but that doesn't change the conclusion), so a parent that does have a parent
Now we have 2 objects A, one with a parent, and one without a parent, which Core Data consider has 2 objects.
The easy way out: my tree is a nested set, so I just have to sort places by the left value, and this way I'll always create parents before children.
The "sort by name" wasn't part of my description, so I'll leave scc's answer as accepted :)

Programatically load data finto uitableview using predicate coredata as if table has already had row selected

I'm trying to use the excellent code in http://blog.sallarp.com/iphone-core-data-uitableview-drill-down/ to populate fur pop-overs
// Override point for customization after app launch
NSManagedObjectContext *context = [self managedObjectContext];
// We're not using undo. By setting it to nil we reduce the memory footprint of the app
[context setUndoManager:nil];
if (!context) {
NSLog(#"Error initializing object model context");
exit(-1);
}
// Get the url of the local xml document
NSURL *xmlURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"Data" ofType:#"xml"]];
NSError *parseError = nil;
// Parse the xml and store the results in our object model
LocationsParser *xmlParse = [[LocationsParser alloc] initWithContext:context];
[xmlParse parseXMLFileAtURL:xmlURL parseError:&parseError];
[xmlParse release];
// Create a table view controller
RootViewController *rootViewController = [[RootViewController alloc] initWithStyle:UITableViewStylePlain];
/*
ORIGINAL
*/
rootViewController.managedObjectContext = context;
rootViewController.entityName = #"County";
If I change the entityName to
rootViewController.entityName = #"Province";
It loads the second tier of data into the initial view, but want I want is the load only the second tier of data that belongs to a particular first tier row.
So replacing the two lines I've marked as original with
// Get the object the user selected from the array
NSManagedObject *selectedObject = [entityArray objectAtIndex:0];
// Pass the managed object context
rootViewController.managedObjectContext = self.managedObjectContext;
rootViewController.entityName = #"Province";
// Create a query predicate to find all child objects of the selected object.
NSPredicate *predicate = nil;
predicate = [NSPredicate predicateWithFormat:#"(ProvinceToCounty == %#)", selectedObject];
NSLog(#"selectedObject %#",selectedObject);
It would work if I could work out how the replace entityArray with a reference to the initial loading the data.
Hope that makes sense.
Without any code what I'm hoping to achieve is to use the XML to populate four different pop-over uitableviews, each one has already drilled down a level into the data as it loads. I can get straight to all the second data from all the first tier rows, but that's no good, I only want the second tier data from one first tier row per popoverview.
ie emulate this happening
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Force the data from index 1 of first tier
NSManagedObject *selectedObject = [entityArray objectAtIndex:1];
rootViewController.managedObjectContext = self.managedObjectContext;
NSPredicate *predicate = nil;
rootViewController.entityName = #"Province";
predicate = [NSPredicate predicateWithFormat:#"(ProvinceToCounty == %#)", selectedObject];
NSLog(#"selectedObject %d %#",indexPath.row, selectedObject);
I've learnt a lot in the last four hours but not enough and now I'm defeated...

Resources