I am using Core Data. I retrieve data in a NSMutable array using some NSPredicate. Now I want to update some items. Let say I have a name string or some BOOL which I want to update. So how to do that. Like Is there any way I can update with respect to ID or something, becoz I dont get all the items from the managedObjectContext.
You assign the data to the objects. and then call
[managedObjectContext save:&error];
on each the respective context object. You can call
[managedObject managedObjectContext];
to get that items context
To get a managed object with a certain id you use
NSFetchRequest and it's setPredicate method. then something like this
[request setEntity:[NSEntityDescription entityForName:#"Shirt" inManagedObjectContext:moc]];
NSArray *shirtsInDB = [moc executeFetchRequest:request error:nil];
or this method in NSManagedObjectContext
- (NSManagedObject *)objectWithID:(NSManagedObjectID *)objectID
Related
I have a coredata project that I'm trying to programmatically update a number.
I'm retrieving objects from CoreData and then storing it into an array.
Then, I'm looping through that array to see if the current user's IP is present in the database and trying to update the number of times accessed for that specific array.
The problem is, it's updating all the objects, not just the current object in the looped array.
First, I get the info from core data like so:
- (void)fetchRecords {
// Define our table/entity to use
NSEntityDescription *entity = [NSEntityDescription entityForName:#"IPAddr" inManagedObjectContext:managedObjectContext];
// Setup the fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
// Define how we will sort the records
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"ipDate" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:sortDescriptors];
// Fetch the records and handle an error
NSError *error;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (!mutableFetchResults) {
// Handle the error.
// This is a serious error and should advise the user to restart the application
}
// Save our fetched data to an array
[self setIpArray: mutableFetchResults];
}
Now, I'm trying to find if the current User IP is present in the fetched results, and if it's present, update the number of times accessed:
// see if the ip is present and update if necessary
-(void)ipPresent {
NSString * theCurrentIP = [self getGlobalIPAddress];
for (IPAddr *allips in ipArray)
{
if ([allips.ipNum isEqualToString:theCurrentIP]) {
NSLog(#"The IP %# was found.", theCurrentIP);
// update the ip
NSError *error = nil;
NSNumber *ipToUpdate = allips.ipAccess;
NSNumber *addIpAccess = [[NSNumber alloc] initWithInt:1];
NSNumber *updateIpAddress = [NSNumber numberWithFloat:([ipToUpdate floatValue] + [addIpAccess floatValue])];
[self.ipArray setValue:updateIpAddress forKey:#"ipAccess"];
if ([self.managedObjectContext save:&error]) { // write to database
NSLog(#"The IP Was Updated from %# to %#", ipToUpdate, updateIpAddress);
} else if (![self.managedObjectContext save:&error]) {
NSLog(#"failed with error: %#", error);
}
break;
} else {
NSLog(#"The IP %# was NOT found.", theCurrentIP);
}
}
}
I'm pretty sure the issue is with this line:
[self.ipArray setValue:updateIpAddress forKey:#"ipAccess"];
Again, it's updating ALL the entities and not just the one in the current loop.
Indeed. You are using the wrong method. self.ipArray is a NSMutableArray.
The method
- (void)setValue:(id)value forKey:(NSString *)key
is used for Key-Value Coding (which is what makes it work for Core Data objects), but when applied to an array, it will invoke setValue:forKey: on each entry in the array.
Now, you can see that you could also call setValue:forKey on the one single array element allips since its property is obviously KVC compliant -- otherwise you would be having a different problem, not see the values being set.
Note, that you could also just assign the property...
allips.ipAccess = updateIpAddress;
EDIT
Sorry, probably should have read slower... You do understand that you don't have to use a mutable array, right? You are not actually changing the array, just the elements in the array. An immutable collection means that the collection contents can not change, but when you have a pointer to an object, as long as that object is not immutable, you can still mutate its properties.
Thus, if you had an immutable array of Foo objects, you could do this...
for (Foo *foo in myImmutableArray) {
Bar *bar = [self getSomeNewBar];
[foo setBar:bar];
// If Foo is KVC compliant, you can do this too...
[foo setValue:bar for Key:#"bar"];
}
If, however, you call setValue:forKey on the array, it will be invoked for each element of the array. Note, that setValue:forKey is actually declared in the immutable NSArray.
EDIT
That comment was hard to read.
The core data object is just another object. It looks like you have subclassed it, and provided it with properties for the attributes. Just replace
[self.ipArray setValue:updateIpAddress forKey:#"ipAccess"];
with
[allips setValue:updateIpAddress forKey:#"ipAccess"];
or
allips.ipAccess = updateIpAddress;
Either of those should modify your core data object, as they would any object that had a read/write property named "ipAccess"
Assuming, of course, that I didn't read it wrong again... and allips is your core data object...
I see that there is a method of getting the unique id of a managed object:
NSManagedObjectID *moID = [managedObject objectID];
But, as I have read, that changes when it is saved.
What is a better way of creating my own unique ID and saving it in each object, making sure that it IS unique and does't change?
You cannot save the NSManagedObjectID in a CoreData entity, and there's no properties in it that can be intended as integer or string ID.
Therefore building your own unique identifier algorithm is an acceptable solution, if it's impossible for you to keep track when the entity is saved, I did this for some applications.
For example I had a similiar problem time ago, when I needed to fill a UITableView cells with a reference to the entity, and retrieve the entity after clicking/touching the cell.
I found a suggestion by using the uri representation, but however I still needed to save the context before, but I manage to use the NSFetchedResultsController and found a more solid solution rather than an application built id.
[[myManagedObject objectID] URIRepresentation];
Then you can later retrieve the managed object id itself:
NSManagedObjectID* moid = [storeCoordinator managedObjectIDForURIRepresentation:[[myManagedObject objectID] URIRepresentation]];
And with the moid I could retrieve your managed object.
I have a created date property in my object, so I just ended up using that date including seconds, which, seems to me like it will be unique and work how I want.
You can create an id field for your object, and populate it during init using GUID. for how to create a GUID and optionally export it to string see UUID (GUID) Support in Cocoa
If it helps anyone else searching for this, this is how I do it:
Create an ID property on the object
Get the last used ID when creating an object with this code:
Playlist *latest;
// Define entity to use and set up fetch request
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Playlist" inManagedObjectContext:managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
// Define sorting
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"listID" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
// Fetch records and handle errors
NSError *error;
NSArray *fetchResults = [managedObjectContext executeFetchRequest:request error:&error];
if (!fetchResults) {
NSLog(#"ERROR IN FETCHING");
}
if ([fetchResults count] == 0) {
latestID = 0;
} else {
latest = [fetchResults objectAtIndex:0];
latestID = latest.listID;
}
Increment this by one on the new object.
Maybe I'm not going about showing a detail for a selected row using CoreData, but I can't figure out why I'm getting a "BAD_ACCESS" error. I've googled around and can't find what I'm looking for.
Basically I use CoreData to populate the data for a Table View. It retrieves all of the title attributes for all of the entities. When the user clicks on a row, I have a Detail View that needs to show the description for that entity. I think I need to make a new NSManagedObjectContext and a new NSEntityDescription for a new NSFetchRequest in my DetailViewController and then use a NSPredicate to say "where title = [user selected title]". I get an error when I select a row. See code:
- (void)viewDidLoad
{
// Do any additional setup after loading the view from its nib.
// Get the objects from Core Data database
Caregiver_Activity_GuideAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:#"Definition"
inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"(title = %#)", self.title];
[request setPredicate:pred];
NSError *error;
NSArray *objects = [context executeFetchRequest:request error:&error];
if (objects == nil) {
NSLog(#"There was an error!");
// Do whatever error handling is appropriate
}
for (NSManagedObject *oneObject in objects) {
[definitionDescriptionTextView setText:[oneObject valueForKey:#"desc"]];
}
[objects release];
[request release];
[super viewDidLoad];
}
I comment out that code and everything works. But when I try to debug with breakpoints, nothing catches. So I'm more confused.
I know CoreData is probably overkill for what I'm doing but this is a learning app for me.
EDIT: I didn't include that I'm using a sqlite database that is pre-populated with the entities.
You can also download my project on my github page.
Normally, with a Core Data backed Master-Detail interface, you don't fetch for the Detail view.
When you select a row in the Master tableview, you are selecting a particular managed object instance. You then pass that managed object instance to the detail view. There is no need to refetch the object that you selected in the tableview.
A good example of this would be the Contacts app. The Master tableview would be a list of Contact objects (displaying the name.) When you select a row, the Master tableview controller takes the specific Contact object associated with the selected row and then passes it to the Detail view controller which then populates the Detail view using data taking from the properties of the passed Contact object.
So, that entire code block where the error occurs is unnecessary.
However, the immediate error in this code is that you are releasing an object you didn't create. In this line:
NSArray *objects = [context executeFetchRequest:request error:&error];
... you are not creating a NSArray instance with a init, new or create method. Instead, you are merely receiving an autoreleased NSArray instance created and returned by the context NSManagedObjectContext instance. When you release an object you did not create here:
[objects release];
... you cause the crash.
Conversely, you do create a NSFetchRequest here:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
... because you used init so you do have to balance that with:
[request relwase];
BTW, this type of code should not be put in viewDidLoad as the method is only called when the view is read in the first time from the nib file on disk. That is only guaranteed to happen once as the view may remain in memory when the user switches to another view. Instead, put code that needs to run each time the view appears in viewWillAppear.
Is there a method similar to insertNewObjectForEntityName that edits the current entity being passed in managed object context? I don't want to create another duplicate entity.
In addition, I don't want users to be able to enter two entities with identical attributes (one attribute, the event title). How can I make it so an alert pops up when they try to add a new entity with an identical title attribute?
Your first question it sounds like what you want to do is get an object that is already in the context with a fetch request, change some values on the object then call the -save method on your context.
For the second part, what you would do is when the user tries to add an item, search the context for an object with the same title, if the item exists, pop up an alert.
Edit: here is some code from my app (edited a bit) in which I set up and execute a fetch request:
NSFetchRequest *categoryRequest = [[NSFetchRequest alloc] init];
[categoryRequest setEntity:[NSEntityDescription entityForName:#"Category" inManagedObjectContext:[self managedObjectContext]]];
NSString *categoryName = #"Cooking";
NSPredicate *categoryNameMatchesPredicate = [NSPredicate predicateWithFormat:#"name MATCHES %#", categoryName];
[categoryRequest setPredicate:categoryNameMatchesPredicate];
NSError *error = nil;
NSArray *categoryArray = [[self managedObjectContext] executeFetchRequest:categoryRequest error:&error];
After this request, the array categoryArray contains all category entities with the name "Cooking". If there are no entities with the name "Cooking" the array will be empty.
It is probably faster to use -countForFetchRequest:error: and check for nonzero count before you actually execute the fetch request, but I am not sure it matters that much in a smallish iOS app.
So im really new to core data, but i went through a tutorial and pretty much understand it, well at least the idea behind most of the things. But I still have 1 question that i cant find anywhere. It seems really simple but here it is. If I were to have two strings inside one entity lets say:
1.name
2.position
If the name is already entered how might i allow a user to enter text into a textField and assign it to their position at a later time? Even if there were 20 names, considering no duplicates?
I was thinking it might be something like this...But it doesnt seem to work.
UserInfo *userInfo = (UserNumber *)[NSEntityDescription insertNewObjectForEntityForName:#"UserInfo" inManagedObjectContext:managedObjectContext];
if ([userName isEqualToString:"#James"]) {
userInfo.Position = nameField.text;
}
On the code above you are casting (UserNumber*) to an object that you are declaring as (UserInfo*)? Which is what and is there any reason why you are doing that?
If I understand your question correctly, you want to create a record with only the username pre-populated and then allow that record to be updated at a later stage.
I will assume your entity is called UserInfo and that there are 2 NSString properties created for it - userName and position. I also assume you have created the class files for UserInfo and imported the header into the relevant view controllers.
Here's how you would do it:
1) Firstly, assuming you have username typed in a UITextField *userNameField, let's create a new record.
UserInfo *userInfo = (UserInfo*)[NSEntityDescription insertNewObjectForEntityForName:#"UserInfo" inManagedObjectContext:self.managedObjectContext];
[userInfo setValue:userNameField.text forKey:#"userName"];
This will create a new instance of UserInfo in your managed object context and set the value of userName to the value on userNameField.text
Then at a later stage a user will get to a point where they can update their records in your app (you may need to think about authentication somewhere here). You will fetch the record that matches your specified username:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSPredicate *userNamePredicate = [NSPredicate predicateWithFormat:#"(userName == %#)", userNameField.text];
[fetchRequest setPredicate:userNamePredicate];
NSEntityDescription *userInfo = [NSEntityDescription entityForName:#"UserInfo" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:userInfo];
NSError *error;
NSArray *fetchRequestArray = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
If the fetchRequest found match(es) to your userNameField.text paramater, they will be saved in the fetchRequestArray. There should only be a maximum of one object there if you take the necessary steps to make the userName property mandatory AND unique.
Access the object by grabbing the objectAtIndex:0 in the array and change it's position property:
UserInfo *userInfoToBeEdited = [fetchRequestArray objectAtIndex:0];
[userInfoToBeEdit setValue:positionTextField.text forKey:#"position"];
In both cases above, remember to invoke CoreData's save method when you are ready to commit your changes. Before save is invoked your changes are only kept in your managed object context which is basically a scratch pad for your persistent data.
[EDIT TO ADD SAVE METHOD]
As per your comment, I usually have the save method below in my AppDelegate (copy/paste directly from Apple template)
- (void)saveContext
{
error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil)
{
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error])
{
[self seriousErrorAlert];
}
}
}
And then whenever I need to save changes, from any view controller I simply grab a reference to my AppDelegate and fire it off:
AppDelegate *theDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
[theDelegate saveContext];