I'm new to Core Data, so go easy on me. :) I'm currently using iOS 7 in Xcode 5.
I have two Entities: Aircraft and Entry. There is a one aircraft to many entry relationship.
I'm trying to save a new Entry which has an aircraft object in it. Here is my Entry.h file (minus the #import):
// Entry.h
#class Aircraft;
#interface Entry : NSManagedObject
#property (nonatomic, retain) NSNumber *duration;
#property (nonatomic, retain) NSDate *flightDate;
#property (nonatomic, retain) Aircraft *aircraft;
#end
Meanwhile, over in my view controller where I save the new Entry, I have the following code. I use a dictionary to set up my data prior to creating a new record:
//Add New Entry
Aircraft *aircraft = [Aircraft new];
aircraft.aircraftRegistration = _aircraftRegistrationTextField.text;
NSDictionary *newEntry = #{
#"aircraft": aircraft, //???
#"flightDate": [formatter dateFromString:_flightDateTextField.text],
#"duration": [PPHelpers convertHHMMtoDecimal:_durationTextField.text],
};
//Save new Entry to Core Data
PPAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSManagedObject *item = [NSEntityDescription insertNewObjectForEntityForName:#"Entry" inManagedObjectContext:context];
[item setValuesForKeysWithDictionary:newEntry];
NSError *error;
[context save:&error];
I get a couple errors from this:
CoreData: error: Failed to call designated initializer on NSManagedObject class 'Aircraft'
and...
-[Aircraft setAircraftRegistration:]: unrecognized selector sent to instance 0x110b0b550
I suspect the issue is that I'm not adding the Aircraft to the Entry in the right way.
Any ideas what I'm doing wrong?
Aircraft *aircraft = [Aircraft new];
creates an object without calling the designated initializer. You should replace it by
Aircraft *aircraft = [NSEntityDescription insertNewObjectForEntityForName:#"Aircraft" inManagedObjectContext:context];
Note: It is possible to set Core Data attributes with setValuesForKeysWithDictionary,
but a more direct, type-safe and less error-prone way would be to use the Core Data
accessors:
Entry *entry = [NSEntityDescription insertNewObjectForEntityForName:#"Entry" inManagedObjectContext:context];
entry.aircraft = aircraft; // from above
entry.duration = ...;
entry.flightData = ...;
if Aircraft is an parent relation entity, you need to use instanciate it by insertNewObjectForEntityForName it's the same that Entry.
//Save new Entry to Core Data
PPAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
//Add New aircraft
Aircraft* aircraft = (Aircraft*) [NSEntityDescription insertNewObjectForEntityForName:[NSClassFromString(Aircraf) inManagedObjectContext:context];
aircraft.aircraftRegistration = _aircraftRegistrationTextField.text;
Entry* item = (Entry*) [NSEntityDescription insertNewObjectForEntityForName:NSClassFromString(Entry) inManagedObjectContext:context];
[item setAircraft:aircraft];
[item setFlightDate:[formatter dateFromString:_flightDateTextField.text]];
[item setDuration:[PPHelpers convertHHMMtoDecimal:_durationTextField.text]];
NSError *error;
[context save:&error];
Related
I'm a bit confused about saving entities using Core Data. I'm making a screen that will allow users to save their settings (contact information), which can be changed later if they wish.
From what I understand, my code below will save multiple entities each time the 'save' button is pressed.
- (IBAction)saveSettings:(id)sender {
AppDelegate *appDelegate =
[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context =
[appDelegate managedObjectContext];
NSManagedObject *userSettings;
userSettings = [NSEntityDescription
insertNewObjectForEntityForName:#"UserSettings"
inManagedObjectContext:context];
[userSettings setValue: _firstName.text forKey:#"firstName"];
[userSettings setValue: _lastName.text forKey:#"lastName"];
[userSettings setValue: _userEmail.text forKey:#"userEmail"];
[userSettings setValue: _zipCode.text forKey:#"zipCode"];
}
What I don't understand how to do is save one entity, and then change the values of the attributes later on whenever the user types in new values in the appropriate text fields and presses 'save'.
Yes - because you use insertNewObjectForEntityForName:, a new UserSettings object is created each time that method is run. What you probably want to do is to fetch the existing settings from the database, update your textFields with that data, present the view and let the user amend the details as necessary, and then (when they press the save button), save that data back to the database.
I would add userSettings as a property:
#property (strong, nonatomic) NSManagedObject *userSettings;
and in your method delete the declaration of userSettings, and the line where you use insertNewObjectForEntityForName.
Then create a new method to handle fetching the data from the database and assigning it to your textFields, as follows:
-(void)loadSettings {
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:#"UserSettings"];
NSError *error;
NSArray *results = [context executeFetchRequest:fetch error:&error];
if (results == nil) {
// some error handler here
}
if ([results count] > 0) {
userSettings = [results lastObject];
_firstName.text = [userSettings valueForKey:#"firstName"];
_lastName.text = [userSettings valueForKey:#"lastName"];
_userEmail.text = [userSettings valueForKey:#"userEmail"];
_zipCode.text = [userSettings valueForKey:#"zipCode"];
} else {
// set your text fields to some defaults values??
}
}
Call this method when your view controller loads, in the viewDidLoad method. I've assumed that you will normally have only one UserSettings object (hence lastObject will be the only object!). If you could have many UserSettings objects, you would need to filter the fetch to get only the one you want. To do that you would need to set a predicate for the fetch - look at the documentation for NSPredicate.
You are actually overwriting those properties everytime you "set". The correct way to store individual properties is to assign them and save, like so:
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSManagedObject *userSettings;
userSettings = [NSEntityDescription insertNewObjectForEntityForName:#"UserSettings"
inManagedObjectContext:context];
userSettings.firstName = _firstName.text;
userSettings.lastName = _lastName.text;
userSettings.userEmail = _userEmail.text;
userSettings.zipCode = _zipCode.text;
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Error Saving: %#", error);
}
I have two entities with a to-many relationship, there can be multiple items to each list.
List
Item
Basically all I want to achieve is to to save an Item to a specific List on click.
I've figured out how to save a new list:
- (IBAction)handleBtnAddList:(id)sender
{ MyListAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSManagedObject *newList;
newList = [NSEntityDescription
insertNewObjectForEntityForName:#"List"
inManagedObjectContext:context];
[newContact setValue:#"Shopping" forKey:#"name"];
NSError *error;
[context save:&error]; }
But how do I save an item to the newly created list "Shopping" ?
Any help appreciated. Thanks in advance!
Hi there, finally had a chance to try this out. It's working now but running into another issue. I can add a new list, but can't seem to add an item.
I looked in the sqlite database and under the 'Item' entity the column named 'zLists' is empty. How do I get a value in there that will correspond with the 'List' that the item should be under?
This is what I've got
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSManagedObject *list = [NSEntityDescription
insertNewObjectForEntityForName:#"List"
inManagedObjectContext:context];
[list setValue:#"Test List" forKey:#"name"];
NSManagedObject *item = [NSEntityDescription
insertNewObjectForEntityForName:#"Item"
inManagedObjectContext:context];
[item setValue:#"Test Item" forKey:#"name"];
I also tried adding this at the end but it crashes the app
[item setValue:#"Test List" forKey:#"lists"];
For my example to work with your code, you will need these prerequisites...
You have a Core Data model NSManagedObjectModel,
Within that model, you have an entity List,
Within that model and for that entity, you have two attributes listDate, and listText,
You have used Xcode to prepare (or have manually prepared) an NSManagedObject subclass for your entity List.
Instead of your line of code: [newContact setValue:#"Shopping" forKey:#"name"];...
you might choose to use dot notation to set attribute values in this manner...
newList.listDate = [NSDate date]; //sets the attribute to current date and time
newList.listText = <<some user input from (for example) a view controller>>;
or to match your syntax, you might choose to use key-value coding in this manner.
[newList setValue:[NSDate date] forKey:#"listDate"];
[newList setValue:<<some user input>> forKey:#"listText"];
I'm fairly new to coredata and have been stuck on an issue.
Any assistance will be greatly appreciated.
Information:
I have a core data app with to entities;
List and Task.
List and Task have a one-to-many relationship.
Task.h
#class List;
#interface Task : NSManagedObject
#property (nonatomic, retain) NSString * task;
#property (nonatomic, retain) NSString * note;
#property (nonatomic, retain) List *list;
#end
List.h
#class Task;
#interface List : NSManagedObject
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSDate * dateCreated;
#property (nonatomic, retain) NSNumber * sortOrder;
#property (nonatomic, retain) NSSet *task;
#end
#interface List (CoreDataGeneratedAccessors)
- (void)addTaskObject:(Task *)value;
- (void)removeTaskObject:(Task *)value;
- (void)addTask:(NSSet *)values;
- (void)removeTask:(NSSet *)values;
#end
I create lists using;
NSManagedObjectContext *context = [self managedObjectContext];
if ([self.listTextField.text length] == 0) { // Quit here if no text is entered
[self dismissViewControllerAnimated:YES completion:nil];
return;
}
// Create a new list.
// Create an NSManagedObject for our database entity.
list = [NSEntityDescription insertNewObjectForEntityForName:#"List" inManagedObjectContext:context];
// Add the new task to the object (which in turns adds to our database).
[list setValue:self.listTextField.text forKey:#"name"];
// Get current date and time.
NSDate *todayDate = [NSDate date];
// Add the date to the object (which in turns adds to our database).
[list setValue:todayDate forKey:#"dateCreated"];
NSError *error = nil;
// Save the object to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
[self dismissViewControllerAnimated:YES completion:nil];
I can update already created lists using the code below with cellaccessorybuttontapped;
NSManagedObjectContext *context = [self managedObjectContext];
if ([self.listTextField.text length] == 0) {
// Delete object from database.
[context deleteObject:self.list];
NSError *error = nil;
// Save the action to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
[self dismissViewControllerAnimated:YES completion:nil];
} else {
// Update existing task.
[self.list setValue:self.listTextField.text forKey:#"name"];
// Get current date and time.
NSDate *todayDate = [NSDate date];
// Add the date to the object (which in turns adds to our database).
[list setValue:todayDate forKey:#"dateCreated"];
NSError *error = nil;
// Save the action to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
}
[self dismissViewControllerAnimated:YES completion:nil];
I can then navigate into a list.
My question is how can I then create a task for the list I have just navigated into?
It's been 2 days and I've not been able to find anything on Google.
As advised I have added;
#property (strong, nonatomic) List *selectedList;
I now have this as my Save method
NSManagedObjectContext *context = [self managedObjectContext];
// Saving a new task.
Task *task = [NSEntityDescription insertNewObjectForEntityForName:#"Task" inManagedObjectContext:context];
task.task = self.taskText.text;
task.note = self.noteText.text;
task.list = self.selectedList;
NSLog(#"The selected list is: %#", [self.selectedList description]);
NSError *error = nil;
// Save the object to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
[self.selectedList addTaskObject:task];
[self dismissViewControllerAnimated:YES completion:nil];
The new task is created but it is created in all lists.
Is it possible that this is working and I'm not fetching tasks based on their list?
This is my fetch request when I navigate into a list:
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
// Create a fetch request.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Create an entity so fetch the data from.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Task" inManagedObjectContext:self.managedObjectContext];
// Set the entity of the fetch request.
[fetchRequest setEntity:entity];
// Set the amount to be fetched at a time
[fetchRequest setFetchBatchSize:20];
// Create a sort descriptor.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"task" ascending:NO];
// Attach the sort descriptor to the fetch request.
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Create a fetch result controller using the fetch request
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
theFetchedResultsController.delegate = self;
// Perform fetch.
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
// Handle error.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
return fetchedResultsController;
That is what these methods in your entity header are for:
- (void)addTaskObject:(Task *)value;
- (void)removeTaskObject:(Task *)value;
- (void)addTask:(NSSet *)values;
- (void)removeTask:(NSSet *)values;
You'll create a new task entity:
Task *t = [NSEntityDescription insertNewObjectForEntityForName:#"Task" inManagedObjectContext:context];
Then complete the fields in it as you did with your list object:
t.task = #"Whatever";
t.note = #"Whatever Note";
t.list = currentlySelectedListItem; // whatever that happens to be -- it will be a (List *)something
Then, you want to add the task object to the list:
[currentlySelectedListItem addTask:t];
Then save the context & you're done.
Key thing here is that you're effectively updating the List object by adding a task to the set of Task values contained in the NSSet. And t.list is going to contain a pointer to the parent List object.
Looks to me like you have it laid out just fine (I'm assuming the:
#property (nonatomic, retain) List *list;
Is a relationship to the parent List and not just another value you have defined; that looks to be the case).
You need to keep track of the list that you have navigated into. One way of doing it would be to create a property called as parentList like so
#property(nonatomic,strong) List *parentList
in the view controller you create a task in. And just before navigating to the view controller set this property.
In the task view controller you do a insert similar to the List object using Task *reqdTask = [NSEntityDescription insertNewObjectForEntityForName:#"Task" inManagedObjectContext:context]; and then set all the values once say Save button is pressed.
[parentList addTaskObject: reqdTask];
and ur done. This will create a Task in the task entity and map it to the List entity. Hope this helps.
**EDIT***
You need to do this [parentList addTaskObject: reqdTask]; before saving your context.
Add this in the NSFectResultsController
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"list = %#",self.parentList]];
so it will be something like this
// Create a fetch request.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Create an entity so fetch the data from.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Task" inManagedObjectContext:self.managedObjectContext];
// Set the entity of the fetch request.
[fetchRequest setEntity:entity];
// Set the amount to be fetched at a time
[fetchRequest setFetchBatchSize:20];
// Create a Predicate.
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"list = %#",self.parentList]];
//continue
this will bring the task associated with the selected list.
I have an issue with Core Data. I have an entity called "Group", and other entity called "Contact". These entities have a relationship "many to many" between them, because a group could have many contacts (members of the group), and a contact could have many groups (be a member of many groups).
So, this is the relationship:
Group <<----->> Contact
What I need is insert contacts (new or existing) as members of existing groups.
What is my issue? Well...I am able to insert them if I create a new contact, but if the contact already exists, Core Data does not overwrite and save it.
Here is my code:
- (void)saveMemberInGroup:(Message *)message
{
GroupInfo *groupInfo = message.group;
UserInfo *member = message.user;
AppDelegate *app = (AppDelegate *)[UIApplication sharedApplication].delegate;
NSManagedObjectContext *context = [app managedObjectContext];
NSError *error;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:CONTACT_ENTITY
inManagedObjectContext:context];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"ident == %#", [member ident]]];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
//Here I check if the contact exists.
Contact *contact;
if ([fetchedObjects count] == 0) {
contact = [NSEntityDescription insertNewObjectForEntityForName:CONTACT_ENTITY
inManagedObjectContext:context];
[contact setIdent:[NSString stringWithFormat:#"%#", [member ident]]];
} else {
contact = [fetchedObjects objectAtIndex:0];
}
//Now I make a new request for the group.
fetchRequest = [[NSFetchRequest alloc] init];
entity = [NSEntityDescription entityForName:GROUP_ENTITY
inManagedObjectContext:context];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"ident == %#", [groupInfo ident]]];
[fetchRequest setEntity:entity];
fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
Group *group = [fetchedObjects objectAtIndex:0];
//Now, I overwrite the group and the contact.
[[group members]addObject:contact];
[[contact memberGroups]addObject:group];
[context save:&error];
}
Here is my Group entity:
#interface Group : NSManagedObject
#property (nonatomic, retain) NSString *ident;
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) NSMutableSet *members;
#end
And my Contact entity:
#interface Contact : NSManagedObject
#property (nonatomic, retain) NSString *ident;
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) NSMutableSet *memberGroups;
#end
NOTE:
This is really weird...when I check if I have updated the objects successfully, everything seems to be ok, both groups and contacts have their NSSet updated rightly, but when I restart the app (I mean, finalize and start it again, or press "play" in XCode), Core Data only has saved the relationship with the new contacts created!!.
Example:
Create Contact1 and save in Group1.
Create Contact2 and save in Group1.
Existing Contact1 and save in Group2.
Create Contact3 and save in Group2.
If I check Core Data when my app is running, I will have in Group1 (Contact1, Contact2), and in Group2 (Contact1, Contact3).
Then I restart the app and check again Core Data, and now I have in Group1 (Contact1, Contact2), and in Group2 (Contact3)!! Core Data has lost the relationship between Contact1 and Group2, and this happens every time I have a relationship between a group and an existing contact.
What I am doing wrong??
Thanks a lot!.
You should create the class files for your entities with "Editor -> Create NSManagedObject
Subclasses ..." from the Xcode menu. A to-many relationship is represented by an NSSet,
not a NSMutableSet, so you cannot modify it directly by adding an object.
Once you have done that, you can add an additional object to group.members with
[group addMembersObject:contact];
or alternatively
[contact addMemberGroupsObject:group];
Core Data updates inverse relationships automatically, so it does not matter
which one you choose.
I have from one side the NSManagedObject event that has a relationship one to many with the Entity Contacts.My app downloads contacts from the server, once all the contacts are downloaded I want to save them in CoreData keeping the relationship. Im wondering if I can save them in one go. All the contacts in an NSArray, contactsWeb, and push them into Core Data or I have to save each one of them like this:
NSManagedObjectContext *context = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
for(int i = 0; [contactsWeb count]; i++){
NSManagedObject *contact = [NSEntityDescription insertNewObjectForEntityForName:#"Contacts" inManagedObjectContext:context];
[contact setValue:[contactsWeb objectAtIndex:i] forKey:#"text"];
[contact setValue:[NSDate date] forKey:#"date"];
[[event mutableSetValueForKey:#"toContacts"]addObject:contact];
NSError *error;
if(![context save:&error]){
NSLog(#"%#", [error localizedDescription]);
}
}
Saving a managed object context means that all changes in the context are saved to the persistent store (or to the parent context, in the case of nested contexts). So you can (and should) save the context "in one go" after all contacts have been inserted and the relationships been set.
Remark: If you create managed object subclasses Contacts and Event for your entities, your code can be simplified to
Contacts *contact = [NSEntityDescription insertNewObjectForEntityForName:#"Contacts" inManagedObjectContext:context];
contact.text = [contactsWeb objectAtIndex:i];
contact.date = [NSDate date];
[event addToContactsObject:contact];
The managed object subclasses can be created in Xcode: Select the entities in the Core Data model editor and choose "Editor -> Create NSManagedObject subclass ..." from the menu.