Removing a NSData stored in CoreData - iphone

I saved a NSData (of UIImage) in an attribute of CoreData.
There are still some other attributes under the same entity.
How can I remove just the NSData stored, but not removing the whole NSManagedObject?
I tried overwriting it to nil, but the size of DB did not change at all, which means the NSData is not removed?

It means the amount of space used to store some value for that attribute is still there, but it does not mean that you did not successfully obliterate the value. Did you use [managedObjectContext save:&error] after writing to the value? If so and it reported no error then the value is gone.
(Does the size of the store really increase when you set an attribute? I expect it would when you add an entity but not just for setting an attribute.)

if you are using tableview to display your CoreData and edit current indexPath in an new VC, try YOURCOREDATA.setNilValueForKey("YOUR IMAGE KEY").
i'm just a newbee of iOS dev but it works fine to me at least.

Related

What's wrong with my UserDefault code for global Array?

Trying to store an array through UserDefault, but Xcode gives me an error. The error message is Thread 1: Signal SIGABRT, and the console says "NSInvalidArgumentException, reason: Attempt to insert non-property list object". I have previously stored data in the array using this code:
let tempRecipe = GlobalFavorites(recipeImageObject: "", recipeTextObject: "", recipeHeaderObject: "", favoriteRecipeArray: [globalFavoriteRecipes])
tempRecipe.recipeHeaderObject = self.recipeClassArray[self.currentView].recipeHeaderObject
tempRecipe.recipeTextObject = self.recipeClassArray[self.currentView].recipeTextObject
tempRecipe.recipeImageObject = self.recipeClassArray[self.currentView].recipeImageObject
globalFavoriteRecipes.favoriteRecipeArray.append(tempRecipe)
And that works fine. Here's the code for storing with UserDefault that gives me the error:
UserDefaults.standard.setValue(globalFavoriteRecipes.favoriteRecipeArray, forKey: "savedFavoriteArray")
It's a global array and I want to store the whole array. I guess it has to do with how I write the array in UserDefault, because to me it seems that I'm trying to store something that's not there. Or what am I missing?
If globalFavoriteRecipes.favoriteRecipeArray is an array of custom objects, you need to make sure you are archiving and unarchiving them properly. Refer to this StackOverflow post.
That post also touches on some other options, as NSUserDefaults isn't the best place to store an array that can potentially grow pretty large, which it sounds like it could in this case based on the variable name.
Use set not setValue:
UserDefaults.standard.set(globalFavoriteRecipes.favoriteRecipeArray, forKey: "savedFavoriteArray")
But, I agree with #Jake, this should only be used to store small amounts of data, not a large array. Happy coding!

iOS Core Data - Updating Multiple records

I've scoured and still haven't found anything that quite works. Either the question/answer is too old or it simply hasn't worked for me. This is my first attempt at "my own" app. As it seems a right of passage, I'm making a checklist app. Here's what I'm looking for:
My Data Store contains 4 attributes: name, category, isChecked, isActive (more will surely follow as I expand)
When my View Controller initially loads, the NSFetchedResultsController has an NSPredicate that only retrieves the records whose attribute isActive is YES (or [NSNumber numberWithBool:YES). It then takes those records and displays them into the appropriate cells for the user. When a user clicks on a cell, the Data Store updates and changes the isChecked attribute accordingly. Everything works good to this point.
What I need to do now is to be able to remove the items (1 or more) from the list. Specifically, I need it to update the Data Store attributes isChecked and isActive to NO only if it's current isChecked attribute is YES. (I'm not looking to delete the record from the data store as they will be used to build up the database for the users future use.)
I've used, among other things:
[[[self fetchedResultsController] fetchedObjects]
setValue:[NSNumber numberWithBool:NO]
forKey:#"isChecked"];
This does actually work, it removes the checkmark(s) and updates the store accordingly. Problem is, not only am I making another request to the data store for the isActive items, it also searches the entire "Active List" that was fetched and sets each of their isChecked attributes to NO. This may not be too big of an issue for small lists, but as the list(s) expand this can be an issue.
The other problem is, if I add:
[[[self fetchedResultsController] fetchedObjects]
setValue:[NSNumber numberWithBool:NO]
forKey:#"isActive"];
It sets ALL of my list items to NO (as well as a second data store request within the same method.)
So my question is: How can I get through the list, find only the items that are checked and update only those records (set both the isChecked && isActive attributes = NO) whose isChecked attribute is YES rather than working through the entire list?
I've tried creating a separate fetchedResultsController specifically for this buttons action, and it did work (that is to say, it didn't crash) but the debugger popped out a rather large 'Serious Application Error'. I won't post the error message as it's long and most likely irrelevant to any solution.
Any assistance would be greatly appreciated. Thanks in advance and please be gentle :-].
EDIT
I have tried using a for loop, for (NSString *item in fetchedResultsController) but I get the error ...may not respond to 'countByEnumeratingWithState:objects:count'
It seems a loop of sorts is what's needed here, but again, nothing I can find is relevant or it's outdated. Again, thanks for any assistance.
Edit 2
Here is the original error I got when I ran a second separate fetchRequestController for this button/method:
An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (4) must be equal to the number of rows contained in that section before the update (4), plus or minus the number of rows inserted or deleted from that section (0 inserted, 3 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null)
You can just loop over the fetchedObjects collection and change the managed objects. After changing them you'll need to reload your list (I guess you use a tableview).
I don't know what your classes are named, but in general you can just loop over the collection of managed objects and change them. Remember that you need to save your managed object context if you want to keep these changes for when the app closes.
NSArray* myCollection = [[self fetchedResultsController] fetchedObjects];
for(ActiveListData *managedObject in myCollection)
{
if(managedObject != nil && managedObject.isChecked)
{
managedObject.isChecked = NO;
managedObject.isActive = NO;
}
}
If you want to do the check on all object in the database you'll need a new method in your NSFetchedResultsController that has a predicate checking on isChecked and then loops over and edits the result collection.
You might want to post your error code as we could be able to point out what you did wrong.
Edit: If you're not familiar with using Core Data the apple documentation provides a lot of information: http://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/CoreData/Articles/cdBasics.html
Thanks to #ggfela for his answer. The processes of his answer were spot on. Here is the actual code I put into my button/method, in hopes of it helping someone else in the future:
NSArray *moc = [[self fetchedResultsController] fetchedObjects];
for (ActiveListData *item in moc) {
if (item != nil && item.isChecked.boolValue == 1) {
item.isChecked = [NSNumber numberWithBool:NO];
item.isActive = [NSNumber numberWithBool:NO];
}
}
// Call to Data Store to update the list
NSError *error;
if (![self.managedObjectContext save:&error]) {
FATAL_CORE_DATA_ERROR(error);
return;
Explanation:
Load the contents of the result from calling the fetchedResultsController method into a temporary variable named moc
Use a for loop to cycle through the array of moc. ActiveListData is the NSManagedObject subclass that I created for my Core Data and is the proper place to insert the separated values/attributes from the data store. From there, it's pretty simple, I ensure that item is not nil AND that the item's attribute is the value I need.
NOTE
Core Data does not store the bool values YES and NO but rather 1 and 0, respectively but when you call or compare the values, you simply can not compare the value of item.isChecked because it is being passed back to you as a bool not as an integer. You can not simply compare item.isChecked == YES either since the #property of isChecked is an NSNumber. So, in the case of the if I put item.isChecked.boolValue as this will give me the representing integer for it's bool value, in this case I have it check for a 1 (YES). (Sorry if my explanation is wrong and/or confusing, but this is how I understand it and is the only way this code works.)
Then, setting the new values of these attributes is like you would expect when setting any other variable. The only "tricky" difference with this is that the NSManagedObject subclass sets the #property of the isChecked and isActive to an NSNumber (as mentioned earlier) so in order to send the proper values back to Core Data you use the method numberWithBool of the NSNumber class.
And just in case anyone gets confused by my FATAL_CORE_DATA_ERROR(error) call this is simply a macro that was defined inside the Prefix.pch file to handle my errors from the managedObjectContext. You can use any (or none) error handling you choose.
Thanks again #ggfela for your help!! If anyone else has any other suggestions on how this code should be applied, then please let me know!
You can use NSBatchUpdateRequest to update multiple records
Examples:
https://www.bignerdranch.com/blog/new-in-core-data-and-ios-8-batch-updating/
http://code.tutsplus.com/tutorials/ios-8-core-data-and-batch-updates--cms-22164

Core data edit function

I already have a tableView with data in it. IF you tap a cell/row it pushes to an edit type of view. Is there anyway to edit core data's data other than: By edit, i mean i already have data inserted into my context. I have loaded my data into my view, the user can change the existing data, and re save it.
.h
//Below is the entity/entity's class name 'Amm'
Amm *amm;
.m
-(IBAction)save
{
[self.amm setValue:self.nameField.text forKey:#"name"];
[self.amm setValue:self.nicknameField.text forKey:#"nickname"];
[self.navigationController popViewControllerAnimated:YES];
NSError *error;
if (![self.managedObjectContext save:&error]) {
//Handle Error
}
}
I want this code to work, however the design pattern of my app isnt allowing this code to work for me as it does in other parts of my app. Thank you very much for any and all help!
I assume from what you've said you have:
A table view listing your managed objects
A view where you can edit the values of a managed object
A save button bound to the save method
What's the actual issue? I'm assuming when you tap save that:
The values in self.nameField.text isn't setting self.amm.name
The values in self.nicknameField.text isn't setting self.amm.nickname
Is that right? If so perhaps try the following code to set the managed object values:
self.amm.name = self.nameField.text
self.amm.nickname = self.nicknameField.text
If that's not the issue and you are actually setting the managed object values properly, is it that you just need to refresh the table-view? Perhaps use some NSLog commands to log every step of the applications progress.

Undo core data managed object

I have this code:
Store* store = [NSEntityDescription insertNewObjectForEntityForName:#"Store"];
store.name = #"My Company"
...
Now the store is managed in the context and will be saved when the context is saved, but I have a button where the user can cancel the form where data is collected. How do I undo or remove this from the context? Or am I thinking wrong?
Core Data has built-in support for undo, so you can undo individual changes by sending the -undo message to the context:
[store.managedObjectContext undo];
It also supports -redo. You can undo all changes up to the most recent save using the -rollback method:
[store.managedObjectContext rollback]
as indicated in #melsam's answer.
As mentioned earlier, you can use an undo manager. Or, you could simply use a separate ManagedObjectContext, and do all your changes in there. If you decide to keep them, save the context. If not, simply discard it. A MOC is just a scratch pad for work, and has no impact on the underlying database until saved.
You can't really "detach an entity" but you can cause a managed object to turn back into a fault, losing any changes that have not been saved.
[managedObjectContext refreshObject:object mergeChanges:NO];
Snipped from the documentation...
If flag is NO, then object is turned into a fault and any pending
changes are lost. The object remains a fault until it is accessed
again, at which time its property values will be reloaded from the
store or last cached state.
[store.managedObjectContext rollback];
Undo works only when I create a undoManager(Swift 5):
managedObjectContext.undoManager = UndoManager()
After this configuration you can undo a last change:
managedObjectContext.undo()
Also you could save all data from the user in an array and when the user is ready, you only have to save the array to core data.

Saving Array to PList from downloaded content in table view on iPhone

I copied these posts from a forum where I posted recently, but got no reply.
I have the base for my code all setup, it is a drill down table with navigation controller which loads data into a nsmutabledictionary from a plist with some empty values, on purpose. On command the app then downloads information which is then set, setValue, in NSMutableArray. The issue is that this NSMutableArray is 3 levels down from the root of the plist. I am looking for a way to replace the array in the plist which is loaded and saved in the appdelegate. How do I this? Help is much appreciated.
So that probably didn't make much sense. In the AppDelegate it loads a plist file into an NSMutableDictionary. The plist is like this:
Root
>Title(String)
>Rows(Array)
>>Item 1(Dictionary)
>>>Title(String)
>>>ID(String)
>>>ItemList(Array)
>>>>Item 1(Dictionary)
>>>>>Title(String)
>>>>>ID(String)
>>>>>Value(String)-----------Empty Value which is Downloaded
>>>>Item 2(Dictionary)
>>>>Item 3(Dictionary)
>>>>Item 4(Dictionary)
>>Item 2(Array)
>>Item 3(Array)
>>Item 4(Array)
The Rows array is sent to the RootViewController. When one of these items is selected from the TableView and new instance is created with the table data source set to the ItemList of that selected item. When the user presses a button it downloads the values and using [setValue: forKey:#"Value"] the values are set in the array. Here is where it gets lost. I need to now set this ItemList array back into the main Root dictionary and then that Root dictionary sent back to the AppDelegate, so that on applicationWillTerminate [self.data writeToFile:] can be called and the downloaded information does not need to be downloaded the next time.
I'm not totally sure I understanad what your problem is. Once you've got the new value populated into the array, you just need to use setObject:forKey: on the dictionary to replace the old array.
If you're having trouble getting a reference to the app delegate, you can use [[UIApplication sharedApplication] delegate]
if the problem is that your dictionaries and arrays are nested such that you need to change more than one object to set the value, consider using mutableCopy to produce mutable arrays and dictionaries from the plist on load. Alternatively, maybe think about using a mire-sophisticated data representation, rather than reading and writing plist format.
I believe I might have to use forKeyPath