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.
Related
I'm new to Core Data. I have two entities: USER and IOU.
// User.h
#property (nullable, nonatomic, retain) NSString *name;
#property (nullable, nonatomic, retain) NSSet<NSManagedObject *> *ious;
// Iou.h
#property (nullable, nonatomic, retain) NSDecimalNumber *amount;
#property (nullable, nonatomic, retain) NSDate *date;
#property (nullable, nonatomic, retain) NSString *iouDescription;
#property (nullable, nonatomic, retain) User *user;
In a view controller, I use a predicate to fetch the ious belonging to a user and then I use that to fetch the user for its name. I then display that user's name along with her list of ious.
// PersonViewController.m
// Fetch Request
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Iou"];
// Predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K == %#", #"user.name", self.userName];
[fetchRequest setPredicate:predicate];
// Execute fetch request
NSError *error = nil;
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedContext sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController.delegate = self;
[self.fetchedResultsController performFetch:&error];
Iou *iou = self.fetchedResultsController.fetchedObjects.lastObject;
self.user = iou.user;
Next, my customers can tap on an IOU and edit both the IOU and the user.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Iou *iou = [self.fetchedResultsController objectAtIndexPath:indexPath];
personEditViewController.user = self.user;
personEditViewController.iou = iou;
personEditViewController.managedContext = self.managedContext;
[[self navigationController] pushViewController:personEditViewController animated:YES];
}
Here is how I am editing both the USER entity and the IOU entity.
// PersonEditViewController.m
self.user.name = self.nameField.text;
// Description
self.iou.iouDescription = self.descriptionField.text;
// Date
UIDatePicker *datePicker = (UIDatePicker *)self.dateField.inputView;
self.iou.date = datePicker.date;
// Save
NSError *error;
[self.iou.managedObjectContext save:&error];
I use a NSFetchedResultsController to work with a UITableView. If I ONLY change IOU attributes, I get the right behavior. I get NSFetchedResultsChangeMove; however, if I also update the USER, I get NSFetchedResultsChangeDelete. If I restart, the data looks fine so it's only happening in the view.
I searched through some SO questions but I still don't get it. What's a simple pattern to use so it will always work every time.
I considered not using NSFetchedResultsController and relying on good old NSArray with a UITableView. In that case, every time I CRUD a record, it will refetch the records from DB and I'll use the fetchedResultsController.fetchedObjects.
If you amend the name property of the User object, it will no longer match the predicate - so the FRC will trigger the delete to remove the corresponding row in the table view. (Note that you might expect all the other rows to be deleted, since they also no longer satisfy the predicate, but the FRC only re-evaluates those IOUs which have been updated).
If you want to avoid this, then amend your fetch results controller code to first fetch the User object(s) that match the predicate, then amend the FRC predicate to test whether the IOU belongs to any of those users:
// PersonViewController.m
NSError *error = nil;
NSFetchRequest *userFetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"User"];
NSPredicate *userPredicate = [NSPredicate predicateWithFormat:#"%K == %#", #"name", self.userName];
userFetchRequest.predicate = userPredicate;
NSArray *matchingUsers = [self.managedContext executeFetchRequest:userFetchRequest error:&error];
// Fetch Request
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Iou"];
// Predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K IN %#", #"user", matchingUsers];
[fetchRequest setPredicate:predicate];
// Execute fetch request
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedContext sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController.delegate = self;
[self.fetchedResultsController performFetch:&error];
Iou *iou = self.fetchedResultsController.fetchedObjects.lastObject;
self.user = iou.user;
Since the FRC's predicate is based on the User object itself, not any of its attributes, you can amend those attributes without causing its IOUs to fail to satisfy the FRC's predicate.
so I'm trying to overwrite/update a value saved from core data. when the back button is pressed (gets the textfield data and then overwrites the data using that). But it just keeps adding new data in. Here's my code in the back button:
The IF statement is just checking what the index is so it knows which view controller to go back to. goBackMVC just takes it back to a certain view controller.
- (IBAction)btnBack:(UIBarButtonItem *)sender {
if (self.viewControllerIndex == 3) {
NSLog(#"test");
[self saveDataMethod];
[self goBackMVC];
[self.navigationController popViewControllerAnimated:YES];
}
saveDataMethod:
- (void) saveDataMethod {
NSManagedObjectContext *context = [self managedObjectContext];
// Create a new managed object
FavouriteItem *favouriteItem = [NSEntityDescription insertNewObjectForEntityForName:#"FavouriteEntity" inManagedObjectContext:context];
favouriteItem.webName = self.txtName.text;
favouriteItem.webURL = self.txtURL.text;
favouriteItem.imageURL = self.txtImageURL.text;
NSLog(#"favouriteItem.webName %#", favouriteItem.webName);
NSError *error = nil;
// Save the object to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
}
My question is how can I overwrite the data instead of just adding it? Thanks.
edit: I've searched around and a lot of solutions have arrays, but I'm not allowed to use arrays
This is because you insert a new entity to your core data :
FavouriteItem *favouriteItem = [NSEntityDescription insertNewObjectForEntityForName:#"FavouriteEntity" inManagedObjectContext:context];
Instead fetch the required entity :
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Favorits" inManagedObjectContext:context]];
To get the required entity create an NSPredicate instance to filter the required entity (in case you have more than one) and use it in your request :
NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:<Your filter string>];
[fetchRequest setPredicate:filterPredicate];
NSError *error = nil;
NSArray* entities = [context executeFetchRequest:fetchRequest error:&error];
if ([entities count] == 1) {
// Get the entity and update necessary fields and save in context
}
I'm trying to create a relation between two entities Mensagem and Categories, but i'm getting the following error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unacceptable type of value for to-many relationship: property = "mensagemParaCategorias"; desired type = NSSet; given type = __NSArrayI; ...
).'
What i tried to do was: Create a entity property (categoriaAtual), then try to get the entity by name and them set the entity to the fetched results and them create the relationship .
Here is my code.
createInitialDate.h
#import <Foundation/Foundation.h>
#import "Mensagens.h"
#import "Categorias.h"
#interface createInitialData : NSObject
#property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
#property (nonatomic) Categorias *categoriaAtual;
-(void)createMessage:(NSString *)mensagem ComAutor:(NSString *)autor;
CreateInitialData.m
-(void)createMessage:(NSString *)mensagem ComAutor:(NSString *)autor {
Mensagens *novaMensagem = (Mensagens *)[NSEntityDescription insertNewObjectForEntityForName:#"Mensagens" inManagedObjectContext:self.managedObjectContext];
[novaMensagem setMensagem:mensagem];
[novaMensagem setAutor:autor];
NSDate *agora = [NSDate date];
[novaMensagem setDataCriada:agora];
[self categoria:#"Posts"];
novaMensagem.mensagemParaCategorias = _categoriaAtual; // This is where i try to create the relation between the 2 entities.
NSError *error = nil;
NSManagedObjectContext *context = self.managedObjectContext;
if (![context save:&error]) {
NSLog(#"Error! %#", error);
}
}
-(void)categoria:(NSString *)nomeCategoria {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Categorias" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"categoria == '%#'", nomeCategoria];
[fetchRequest setPredicate:predicate];
NSError *error = nil;
NSArray *fetchedObjects = [[self managedObjectContext]executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(#"Problem! %#", error);
} else {
NSLog(#"Objetos %#", fetchedObjects);
_categoriaAtual = fetchedObjects;
}
}
mensagemParaCategorias
is a to-many relationship but you are assigning a pointer to an object that is not an instance of NSSet. Xcode has an option to generate methods on your managed object subclasses for adding objects to a to-many relationship.
I get this error:
core data data store missing getting error:'NSInvalidArgumentException',
reason: keypath flowData.flowAmount not found in entity <NSSQLEntity FlowData id=1>
when trying to load data from a core data store into a tableview.
The sqlite database is being re-created the next time I run the app after I remove the app from the Simulator after having changed the data model and reversioned it.
I am able to insert into the database. This dump shows the record I just entered:
INSERT INTO "ZFLOWDATA"
VALUES(1,1,1,NULL,8.0,376884478.384176,376884471.83175,8.0);
However when I try to read from the data store I get the above error.
Here's the relevant code for when I'm trying to load the data:
- (void)viewDidLoad
{
[super viewDidLoad];
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
self.title = #"Flow Data Items";
}
and
- (NSFetchedResultsController *)fetchedResultsController {
jhsAppDelegate *appDelegate = (jhsAppDelegate *)[[UIApplication sharedApplication]delegate];
managedObjectContext = [appDelegate managedObjectContext];
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"FlowData" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"FlowData.flowAmount"
ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext sectionNameKeyPath:nil
cacheName:#"Root"];
self.fetchedResultsController = theFetchedResultsController;
self.fetchedResultsController.delegate = self;
return fetchedResultsController;
}
and here's my data model:
and finally, here's my data model class code for the entity:
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#interface FlowData : NSManagedObject
#property (nonatomic, retain) NSNumber * flowAmount;
#property (nonatomic, retain) NSDate * flowEnteredDateTime;
#property (nonatomic, retain) NSDate * flowOccurrenceDateTime;
#property (nonatomic, retain) NSNumber * flowScheduledFrequencyMinutes;
#property (nonatomic, retain) NSNumber * flowUrgency;
#end
It must be something about how I am trying to read in the data since I am able to insert a row on another viewcontroller because I believe that FlowData.flowAmount does exist on the entity as it is being inserted into the entity.
Any ideas? Does the order in which the attributes are listed in the .h file have to match the order in the actual data model? Do my data types match?
The key for your sortDescriptor should be just flowAmount.
No need to also prepend the entity name.
PS: The order of the properties in the class is irrelevant. Your data types match (float and NSNumber).
I have an entity in my core data model like this:
#interface Selection : NSManagedObject
#property (nonatomic, retain) NSString * book_id;
#property (nonatomic, retain) NSString * contenu;
#property (nonatomic, retain) NSNumber * page_id;
#property (nonatomic, retain) NSNumber * nbrOfOccurences;
#property (nonatomic, retain) NSString * next;
#property (nonatomic, retain) NSString * previous;
I have created many Selections and saved them in Core Data and now I would like to delete some selections with some criteria. For example, I would like to delete a Selection object if matches the following:
content = test
page_id = 5
book_id = 1331313
How I can do this?
What Mike Weller wrote is right. I'll expand the answer a little bit.
First you need to create a NSFetchRequest like the following:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Selection" inManagedObjectContext:context]];
Then you have to set the predicate for that request like the following:
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"content == %# AND page_id == %# AND book_id == %#", contentVal, pageVal, bookVal]];
where
NSString* contentVal = #"test";
NSNumber* pageVal = [NSNumber numberWithInt:5];
NSString* bookVal = #"1331313";
I'm using %# since I'm supposing you are using objects and not scalar values.
Now you perform a fetch in the context with the previous request:
NSError* error = nil;
NSArray* results = [context executeFetchRequest:fetchRequest error:&error];
results contains all the managed objects that match that predicate.
Finally you could grab the objects and call a deletion on them.
[context deleteObject:currentObj];
Once done you need to save the context as per the documentation.
Just as a new object is not saved to the store until the context is saved, a deleted object is not removed from the store until the context is saved.
Hence
NSError* error = nil;
[context save:&error];
Note that save method returns a bool value. So you can use an approach like the following or display an alert to the user. Source NSManagedObjectContext save error.
NSError *error = nil;
if ([context save:&error] == NO) {
NSAssert(NO, #"Save should not fail\n%#", [error localizedDescription]);
abort();
}
You should perform a fetch request using an NSPredicate with the appropriate conditions, and then call the deleteObject: method on NSManagedObjectContext with each object in the result set.
In addition to Mike Weller and flexaddicted, after calling [context deleteObject:currentObj]; you need to save: context:
NSError *error = nil;
[context save:&error];
As from documentation:
Just as a new object is not saved to the store until the context is saved, a deleted object is not removed from the store until the context is saved.
That made matter in my case.