Core Data Objective C update content of Entity - objective-c

I am first time asking question here, sorry, but I can not find similar one.
So, I need update data in Entity "City" attribute - #"name".
for Example in my Core Data I already have #"New York", #"Boston".
And by parsing XML I have NSMutableArray *Cities = (#"New York", #"Boston", #"Los Angeles", #"Washington");
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
NSString *attributeString = #"name";
NSManagedObject *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
//save to the TableView
cell.textLabel.text = [[object valueForKey:attributeString] description];
if ((indexPath.row + 1) == numberOfSectionsInTableView && (self.isParsingDone))
[self.insertNewObjectToCities:nil];
//When coredata updating - tableView is also updating automatically
//Here is just adding new data, but I do not know how to update
- (void)insertNewObjectToCities_translation:(id)sender
{
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
NSString *attributeString = #"name";
if (![[self.parseCities.Cities objectAtIndex:i] isEqualToString:[newManagedObject valueForKey:attributeString]])
{
[newManagedObject setValue:[self.parseCities.Cities objectAtIndex:i] forKey:attributeString];
NSLog(#"OBBB %#", [self.parseCities.Cities objectAtIndex:i]);
NSLog(#"dessss %#", [[newManagedObject valueForKey:attributeString] description]);
i++;
if (i==[self.parseCities.Cities count])
{
i = 0;
return;
}
else
{
NSLog(#"valueForKey %#", [newManagedObject valueForKey:attributeString]);
[self insertNewObjectToCities_translation:nil];
}
}
else
{
NSLog(#"else");
return;
}
// Save the context.
NSError *error = nil;
if (![context save:&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();
}
}

To update a managed object, you first need to fetch it, make any changes to the fields in the fetched NSManagedObject, and then save the context you used to fetch the object. If you call insertNewObjectForEntityForName again, it will insert a new managed object every time, even if it already exists in Core Data.
It's quite slow to fetch a single object every time you need to check and see if a new one needs to be added. You might want to cache the objects you currently have loaded (or their unique identifying field) into an NSArray or NSSet so you can check that for membership, instead.

Related

Core Data TableView - Multiple Selection During Edit Mode

I have two TableViews using Core Data. I have an ItemTableview with multiple rows of Item listed by the user, and it allows multiple selection during edit mode. During edit mode, it allows user to delete selected items, or delete all of the items at once. I want the items that's been deleted to be added to a TrashTableView.
Here's what I have so far:
- (IBAction)deleteAction:(id)sender
{
NSManagedObjectContext *context = [self managedObjectContext];
NSArray *selectedRows = [self.tableView indexPathsForSelectedRows];
BOOL noItemsAreSelected = selectedRows.count == 0;
BOOL deleteSpecificRows = selectedRows.count > 0;
if (noItemsAreSelected) {
// Delete all objects from the Core Data.
NSFetchRequest *allItems = [[NSFetchRequest alloc] init];
[allItems setEntity:[NSEntityDescription entityForName:#"Item" inManagedObjectContext:context]];
[allItems setIncludesPropertyValues:NO];
NSError *error = nil;
NSArray *items = [context executeFetchRequest:allItems error:&error];
for (NSManagedObject *object in items) {
[context deleteObject:object];
}
// Add to Trash
for (NSManagedObject *trashObject in items) {
Item *selectedItems = trashObject; <-- #warning -Incompatible pointer types initializing "Items" with an expression of type "NSManagedObject"-
Trash *newTrash = [NSEntityDescription insertNewObjectForEntityForName:#"Trash" inManagedObjectContext:context];
newTrash.trashname = selectedItems.itemname;
newTrash.created = [NSDate date];
// Save the context
NSError *saveError = nil;
if (![context save:&saveError]) {
NSLog(#"Save Failed! %# %#", saveError, [saveError localizedDescription]);
}
}
// Delete from the Array
[_item removeAllObjects];
// Tell the tableView that we deleted the objects.
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView setEditing:NO animated:YES];
}
else if (deleteSpecificRows) {
NSMutableIndexSet *indicesOfItemsToDelete = [NSMutableIndexSet new];
for (NSIndexPath *selectionIndex in selectedRows)
{
[indicesOfItemsToDelete addIndex:selectionIndex.row];
}
// Delete from the Array
[_item removeObjectsAtIndexes:indicesOfItemsToDelete];
// Tell the tableView that we deleted the objects
[self.tableView deleteRowsAtIndexPaths:selectedRows withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView setEditing:NO animated:YES];
}
}
When if (noItemsAreSelected) is called, all of the items are deleted and they all get added to the TrashTableView, but only a first row from the ItemTableView gives the string. So in the TrashTableView, first row has a text but the rest of the rows are just blank cells without any text.
In the debugger, blank cells have NSString trashname = nil; but NSDate created = "2015-01-22 03:41:30 +0000"; has a date.
For else if (deleteSpecificRows) I have no idea how to do it in Core Data....
I've spent quiet a lot of time trying to figure this out, so any help would be greatly appreciated. Thanks!
You are doing things in the wrong order: you currently delete all the items from the context, then iterate through the items, creating the corresponding trash items and saving the context each time. This works OK for the first item. But after the first item, the context has already been saved, so the delete operation (for ALL the items) will have happened, which nils out all their properties. Hence your null values.
I would restructure it as follows:
if (noItemsAreSelected) {
NSFetchRequest *allItems = [[NSFetchRequest alloc] init];
[allItems setEntity:[NSEntityDescription entityForName:#"Item" inManagedObjectContext:context]];
[allItems setIncludesPropertyValues:NO];
NSError *error = nil;
NSArray *items = [context executeFetchRequest:allItems error:&error];
for (Item *trashObject in items) {
// Add to Trash
Trash *newTrash = [NSEntityDescription insertNewObjectForEntityForName:#"Trash" inManagedObjectContext:context];
newTrash.trashname = trashObject.itemname;
newTrash.created = [NSDate date];
// Delete
[context deleteObject:trashObject];
}
// Save the context
NSError *saveError = nil;
if (![context save:&saveError]) {
NSLog(#"Save Failed! %# %#", saveError, [saveError localizedDescription]);
}
Note that changing the cast in the for(Item *trashObject ...) should avoid the compiler warning.
EDIT
For the deleteSpecificRows case, you can use similar code, but using your _item (I assume that is a mutable array which is the datasource for your tableView):
else if (deleteSpecificRows) {
NSMutableIndexSet *indicesOfItemsToDelete = [NSMutableIndexSet new];
for (NSIndexPath *selectionIndex in selectedRows)
{
// First, get the trash object...
Item *trashObject = [_item objectAtIndex:selectionIndex.row];
// Add to Trash
Trash *newTrash = [NSEntityDescription insertNewObjectForEntityForName:#"Trash" inManagedObjectContext:context];
newTrash.trashname = trashObject.itemname;
newTrash.created = [NSDate date];
// and delete the object from the context
[context deleteObject:trashObject];
// and update the list of items to delete
[indicesOfItemsToDelete addIndex:selectionIndex.row];
}
// Delete from the Array
[_item removeObjectsAtIndexes:indicesOfItemsToDelete];
// Tell the tableView that we deleted the objects
[self.tableView deleteRowsAtIndexPaths:selectedRows withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView setEditing:NO animated:YES];
}
Note that this is untested, so might need tidying up....
Longer term, you might want to consider a) using a NSFetchedResultsController to act as your datasource for your tableView, and b) rather than creating separate entities for the trash, add flag to the existing entities (inTrash?) and just changing that to true. Your tableView would then have to show only items with inTrash = false.

Coredata insert - Crash with no stack trace

Hi I'm trying to use core data to insert data using core data. I haven't been able to save my data. As my code stands right now the program crashes without a stack trace so I'm a little stuck
detailedViewController code
-(void) addobject{
if(self.isEditing == false){
MasterViewController *mvc = self.delegate;
mvc.managedObjectContext = self.managedObjectContext;
[mvc insertNewObject: self.txtUrlAddress.text : self.txtUrlName.text :
self.txtUrlImage.text];
}
}
MasterViewController code
-(void)insertNewObject: (NSString *) url : (NSString*) urlName : (NSString *) urlImage
{
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
Event *urlDatabase = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
urlDatabase.url = url;
urlDatabase.urlname = urlName;
urlDatabase.urlImage = urlImage;
// If appropriate, configure the new managed object.
// Normally you should use accessor methods, but using KVC here avoids the need to add a custom class to the template.
//[urlDatabase setValue:[NSDate date] forKey:#"urlname"];
// Save the context.
NSError *error = nil;
if (![context save:&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();
}
int count = [[self.fetchedResultsController sections] count];
NSLog([NSString stringWithFormat:#"Data base size is %d", count]);
}
Can anyone see the issue with my code?
Instead of
-(void)insertNewObject: (NSString *) url : (NSString*) urlName : (NSString *) urlImage
you should write
-(void)insertNewObject:(NSString *) url urlName: (NSString*) urlName urlImage: (NSString *) urlImage
The rest looks fine I think.. Solved this your problem?

Is this always true, that if managedobjectcontext change then something must be deleted, updated, or added?

NSUInteger numberOfChanges = moc.insertedObjects.count + moc.deletedObjects.count+moc.updatedObjects.count;
if (numberOfChanges ==0 )
{
NSAssert([moc hasChanges]==false,#"[moc hasChanges]==false");
return;
}
Somehow the assertion fail. I wonder why. So nothing is inserted. Nothing is deleted. Nothing is updated. Yet [moc hasChanges] is true? This happens very rarely. However, it should not have happened at all.
This is the full code if people want to see.
+(void)commit {
[BGHPTools breakIfLock];
NSManagedObjectContext *moc = [self managedObjectContext];
NSArray * arUpdatedObjects = moc.updatedObjects.allObjects;
NSArray * arUpdatedObjectsID = [arUpdatedObjects convertByPeformingSelector:#selector(objectID)];
NSUInteger numberOfChanges = moc.insertedObjects.count + moc.deletedObjects.count+moc.updatedObjects.count;
if (numberOfChanges ==0 )
{
//NSAssert([moc hasChanges]==false,#"[moc hasChanges]==false");
return;
}
if (arUpdatedObjectsID.count) {
while (false);
}
[BGFetchClass vAddObjectIDCachesForArray:moc.insertedObjects.allObjects];
[BGFetchClass vDeleteObjectsForArray:moc.deletedObjects.allObjects];
/*if (numberOfChanges ==0 )
{
NSAssert([moc hasChanges]==false,#"[moc hasChanges]==false");
return;
}*/
//NSAssert([moc hasChanges],#"[moc hasChanges]==true");
__block NSError *error;
__block BOOL saveSuccesfully;
[moc performBlockAndWait:^{
#synchronized([BGFetchClass class])
{
saveSuccesfully = [moc save:&error];
if (!saveSuccesfully) {
CLog(#"Error in Saving %#", error);
}
else{
}
}
}];
if (![NSThread isMainThread]) {
if (arUpdatedObjectsID.count) { //When we're adding new objects, this won't be called. That is the only time commit is called while we are synching
[BGHPTools vDoForeGroundAndWait:^{
NSManagedObjectContext * moc =[BGMDCRManagedObjectContextThreadHandler managedObjectContext];
for (NSManagedObjectID * moi in arUpdatedObjectsID) {
NSManagedObject * mo = [moc existingObjectWithID:moi error:nil];
NSAssert(mo!=nil, #"Object can't possibly be nil");
[mo turnToFault];
}
}];
}
}
NSManagedObjectContext * parentMoc = [self managedObjectContextMainContext]; //Main parent is not nsmainqueueconcurency type. Hence, this is save
[parentMoc performBlockAndWait:^{
if (![parentMoc save:&error])
{
CLog(#"Error in Saving %#", error);// handle error
}
}];
NSAssert(error==nil, #"Error must be nill");
}
There is one documented situation that can give you this behavior.
From NSManagedObjectContext documentation:
NSManagedObjectContext deletedObjects
Discussion
The returned set does not necessarily include all the objects that have been deleted (using deleteObject:)—if an object has been inserted and deleted without an intervening save operation, it is not included in the set.
Could it be your case?
NSManagedObjectContext.hasChanges turns YES even if you do this:
managedObject.someAttribute = managedObject.someAttribute;
NSManagedObject sometimes fails. try using:
- (BOOL)hasAnythingChanged {
return [[[self changedValues] allKeys] count] > 0;
}
(self being your NSManagedObject)
I found out that changing a property and then changing it back to its last saved value would leave the NSManagedObject dirty. Either that was the intended behavior or I did something wrong along the way. I ended up checking on the [changedValues count] which worked for me but you would have to think about checking for any transient properties yourself since they won't appear in 'changedValues' and will make your NSManagedObject dirty.

CoreData and UITableView: display values in cells

I'm working with Core Data and web service, I want to add my data to my table,
but I don't know how should I call them, would you please help me, since when I used this way it's not working.
Here is my method for update database in my HTTP class
- (void)updateLocalCardsDataBase:(NSArray*) cardsArray
{
//check if current user has cards in local database
NSManagedObjectContext* managedObjectContext = [(AppDelegate*) [[UIApplication sharedApplication] delegate] managedObjectContext];
for(NSDictionary *cardDic in cardsArray)
{
Card *card = [NSEntityDescription insertNewObjectForEntityForName:#"Card" inManagedObjectContext:managedObjectContext];
card.remote_id = [NSNumber numberWithInt:[[cardDic objectForKey:#"id"] intValue]];
card.stampNumber = [NSNumber numberWithInt:[[cardDic objectForKey:#"stampNumber"] intValue]];
card.createdAt = [NSDate dateWithTimeIntervalSince1970:[[cardDic objectForKey:#"createdAt"] intValue]];
[managedObjectContext lock];
NSError *error;
if (![managedObjectContext save:&error])
{
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
NSLog(#"Failed to save to data store: %#", [error localizedDescription]);
NSArray* detailedErrors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
if(detailedErrors != nil && [detailedErrors count] > 0) {
for(NSError* detailedError in detailedErrors) {
NSLog(#" DetailedError: %#", [detailedError userInfo]);
}
}
else {
NSLog(#" %#", [error userInfo]);
}
}
[managedObjectContext unlock];
}
Here is my table:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
// NSManagedObjectContext* managedObjectContext = [(AppDelegate*) [[UIApplication sharedApplication] delegate] managedObjectContext];
static NSString *CellIdentifier = #"CardsCell";
CardCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil){
NSArray *objects = [[NSBundle mainBundle] loadNibNamed:#"CardCell" owner:nil options:nil];
for (id currentObject in objects)
{
if([currentObject isKindOfClass:[UITableViewCell class]])
{
cell = (CardCell *) currentObject;
break;
}
}
NSDictionary *f = [_cards objectAtIndex:indexPath.row];
cell.stampId.text = [f objectForKey:#"stampNumber"];
NSLog(#"%#fdssfdfddavds",[f objectForKey:#"stampNumber"]);
cell.createdAt.text = [f objectForKey:#"createdAt"];
cell.CardId.text = [f objectForKey:#"id"];
return cell;
}
Edit:
My problem is how I can show data in a UITableView
Before call [tableView reloadData], you need to get a data source first. You will get back an array of your data models, not an NSDictionary. You can place the my example method (or a variation that suits you best) where ever best suits your needs, but this one will not filter or sort the models, it will only get all of them. Also, I will place the method in your view controller that stores the table view:
-(NSArray*)getMycards {
NSManagedObjectContext *context = [(AppDelegate*) [[UIApplication sharedApplication] delegate] managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Card" inManagedObjectContext:context];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
NSError *error;
[request setEntity:entityDescription];
NSArray *cards = [context executeFetchRequest:request error:&error];
// now check if there is an error and handle it appropriatelty
// I usually return 'nil' but you don't have if you don't want
if ( error != nil ) {
// investigate error
}
return cards;
}
I recommend creating a property #property NSArray *cards in the view controller where you place your table, it will be easier to manage. One assumption I have made (since I have no other information about your view controller, a property named 'tableView' is declared in your view controller's header file (#property UITableView *tableView;), adjust the naming as needed.
With the above method, when you want to populate your array before loading the table's data:
// you put this block of code anywhere in the view controller that also has your table view
// likely in 'viewDidLoad' or 'viewDidAppear'
// and/or anywhere else where it makes sense to reload the table
self.cards = [self getMyCards];
if ( self.cards.count > 0 )
[self.tableview reloadData];
else {
// maybe display an error
}
Now, your cellForRowAtIndexPath should look like
-(UITableViewCell*tableView:tableView cellForRowAtIndexPath {
UITbaleViewCell *cell = ...;
// creating the type of cell seems fine to me
.
.
.
// keep in mind I don't know the exact make up of your card model
// I don't know what the data types are, so you will have to adjust as necessary
Card *card = self.cards[indexPath.row];
cell.stampId.text = [[NSString alloc] initWithFormat:#"%#",card.stamp];
cell.createdAt.text = [[NSString alloc] initWithFormat:#"%#",card.createdAt];
// you might want format the date property better, this might end being a lot more than what you want
cell.CardId.text = [[NSString alloc] initWithFormat:#"%#",card.id];
return cell;
}
Core Data is extremely powerful, I highly recommend the Core Data overview, followed by the Core Data Programming Guide.

core-data : only the last object can be initialized correctly in a loop

My model has two entities Artist and Album, and Album has a Artist instance member. I used following code to pre-populate my model but only found the last Album , that is ablum3, have set up a correct association with the Artist Beatles. For album1,album2, the artist filed are nil.
There must be something wrong which I did not spot...
//create an artist
NSManagedObject *artist = [NSEntityDescription
insertNewObjectForEntityForName:#"Artist"
inManagedObjectContext:__managedObjectContext];
[artist setValue:#"Beatles" forKey:#"name"];
//populate the data
NSArray *albums = [NSArray arrayWithObjects:#"album1",#"album2",#"album3", nil];
for (NSString *title in albums){
//populate the data
NSManagedObject *album = [NSEntityDescription
insertNewObjectForEntityForName:#"Album"
inManagedObjectContext:__managedObjectContext];
[album setValue:title forKey:#"title"];
[album setValue:artist forKey:#"artist"];
}
Without no further detail, it's difficult to know what is going on. I try to understand the model on what you have written.
So, this model works for me
albums is a to-many relationships to Album. Furthermore it's optional, you can have Artist with no Album.
artist is the inverse rel for Artist. one-to-one cardinality. It's required since you cannot have an Album without an Artist.
Here the code:
- (void)populateDB
{
//create an artist
NSManagedObject *artist = [NSEntityDescription
insertNewObjectForEntityForName:#"Artist"
inManagedObjectContext:[self managedObjectContext]];
[artist setValue:#"Beatles" forKey:#"name"];
//populate the data
NSArray *albums = [NSArray arrayWithObjects:#"album1",#"album2",#"album3", nil];
for (NSString *title in albums){
//populate the data
NSManagedObject *album = [NSEntityDescription
insertNewObjectForEntityForName:#"Album"
inManagedObjectContext:[self managedObjectContext]];
[album setValue:title forKey:#"title"];
[album setValue:artist forKey:#"artist"];
}
}
After having called populatedDB, save the context calling [self saveContext]
- (void)saveContext {
NSError *error = nil;
NSManagedObjectContext *moc = [self managedObjectContext];
if (moc != nil) {
if ([moc hasChanges] && ![moc save:&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();
}
}
}
If you need to arrange your model let me know.
Hope that helps.