I want to make an alarm app, and I have a uitableview, on wich row I have a switch, on or off, now I want to save in core data what user selected, on or off.... Here is my code where I try to save in Core Data...
CoreDataStack *coreDataStack = [CoreDataStack defaultStack];
NSNumber *numberAsBool = [NSNumber numberWithBool:self.switchLabel.on];
self.entry.active = numberAsBool;
[coreDataStack saveContext];
But everytime the value is 0. Anyone have an idea??
Instead of:
NSNumber *numberAsBool = [NSNumber numberWithBool:self.switchLabel.on];
It should be:
NSNumber *numberAsBool = [NSNumber numberWithBool:[self.switchLabel isOn]];
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 am using core data and have been using code like this:
[self.form setValue:self.comments.text forKey:#"comments"];
I want to put code like this into a loop, all my coredata names are the same as the property name. How can I say forKey:self.comments.name and get the same outcome as above or something like that?
EDIT:
If this is not possible, is there another way to set a ton of values into coredata from properties? I have 50+ attributes and properties alike that need to be set and would like to avoid using what im doing now.
If you really want it, you may use these functions from objc/runtime.h:
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount) // To get properties declared by a class.
const char *property_getName(objc_property_t property) // To get the name of one property
Something like this:
unsigned int propCount = 0;
objc_property_t *properties = class_copyPropertyList([self class], &propCount);
for(int idx = 0; idx < propCount; idx++) {
objc_property_t prop = *(properties + idx);
NSString *key = #(property_getName(prop));
NSLog(#"%#", key);
}
There really is no substitute for reading the docs on CoreData as the patterns for use and syntax will not be obvious at all without a little legwork.
That said, you typically fetch an instance of your NSManagedObject subclass from the data store:
NSManagedObjectContext* moc = [delegate managedObjectContext];
NSEntityDescription* description = [NSEntityDescription entityForName:#"Filter" inManagedObjectContext:moc];
NSSortDescriptor* descriptor = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES];
NSFetchRequest* request = [[NSFetchRequest alloc] init];
[request setEntity:description];
[request setSortDescriptors:[NSArray arrayWithObject:descriptor]];
NSError *error;
_enabledFilters = [NSMutableArray arrayWithArray:[moc executeFetchRequest:request error:&error]];
if (error) {
NSLog(#"%#",error.localizedDescription);
}
In this example I now have an array of instances of my NSManagedObject called "Filter"
Then you can select the appropriate instance to reference, and access all of it's attributes with simple dot syntax.
Filter* thisFilter = (Filter*)[_displayFilters objectAtIndex:indexPath.row];
cell.label.text = thisFilter.name;
cell.label.backgroundColor = [UIColor clearColor];
NSString*targetName = thisFilter.imageName;
UIImage *image = [UIImage imageNamed:targetName];
cell.image.image = image;
Now I've taken info from my persistent data store, and used it within my app.
Going the other way and writing to an instance within your data store is only slightly different, in that you directly set the attributes of an instance of your NSManagedObject subclass, and then call save on the context to push any changes down to the store.
TL;DR - you owe it to yourself to spend an hour or two with the CoreData docs...
One way would be to declare an array of the attributes yourself.
NSArray *attributes = [NSArray arrayWithObjects:..., #"comments", .., nil]; // or a NSSet
for(NSString *attribute in attributes){
NSString *text = [[self performSelector:NSSelectorFromString(attribute)] text]; // presuming that it's safe to call 'text' on all your properties
[self.form setValue:text forKey:attribute];
}
Or you can use this if you want all the attributes of your core data model.
I have the following For loop which loops through a Core Data entity and then checks to see if a NSString called keyString matches the attribute. If it does match I then want to set that attribute on the entity to another NSString called value :
NSEntityDescription *entity = [exhibitor entity];
NSDictionary *attributes = [entity attributesByName];
for (NSString *attribute in attributes) {
if ([keyString isEqualToString:attribute]) {
exhibitor.attribute = value; //This bit is wrong.
}
}
The problem is with the line exhibitor.attribute = value. I need to somehow reference the attribute object on exhibitor. Can anyone assist ?
MOs are key value compatible. so:
[exhibitor setValue:value forKey:attribute];
Given an NSManagedObject subclass with a boolean property deleted (this is demonstrated in two different ways with code below since both approaches are not working):
[Code Listing 1]
#interface MyManagedObject : NSManagedObject
#property (nonatomic, retain) NSNumber *deleted;
// Or #property (nonatomic) BOOL deleted;
#end
created and inserted into Core Data as follows:
[Code Listing 2]
metadata.deleted = [NSNumber numberWithBool:NO];
// metadata.deleted = NO;
and fetched
[Code Listing 3]
// setup entity description
NSEntityDescription* entityDescription = [self entityDescription];
// setup the sorter
NSSortDescriptor* sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"createdAt" ascending:YES];
NSSortDescriptor* sortDescriptorSection = [[NSSortDescriptor alloc] initWithKey:#"myManagedObject.category.title" ascending:YES];
// Build request
NSFetchRequest* request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
[request setSortDescriptors:[NSArray arrayWithObjects:sortDescriptorSection, sortDescriptor, nil]];
[request setPredicate:[NSPredicate predicateWithFormat:#"deleted == %#", [NSNumber numberWithBool:NO]]];
// Fetch request
NSArray* items = [[self managedObjectContext] executeFetchRequest:request error:nil];
returns one item in the items array as expected. The problem is when deleted is modified:
[Code Listing 4]
MyManagedObject* myManagedObject; // Assume initialized
myManagedObject.deleted = [NSNumber numberWithBool:YES];
// myManagedObject.deleted = YES;
// Printing description of myManagedObject in debugger shows deleted = 0 at this point
[myManagedObject.managedObjectContext save:nil];
// Printing description of myManagedObject in debugger still shows deleted = 0 at this point
BOOL testValue = myManagedObject.deleted;
if (testValue) {
NSLog(#"value updated"); // This line is executed
}
Re-executing code listing 3 still yields one item in the items array even after a NSFetchResultsController watching the database has fired an update. If the application is terminated and relaunched, re-executing code listing 3 yields no items in the items NSArray.
Calling a Core Data property "deleted" conflicts with the isDeleted property of NSManagedObject.
Compare Core Data NSPredicate "deleted == NO" does not work as expected for a similar problem and some experiments.
Btw. calling a property "updated" causes also problems, compare Cannot use a predicate that compares dates in Magical Record.
You should not be using deleted as a property name for an NSManagedObject subclass.
Also, deleted is an NSNumber, not a BOOL. So, when you are using:
BOOL testValue = myManagedObject.deleted;
if (testValue) {
NSLog(#"value updated"); // This line is executed
}
You are testing if myManagedObject's deleted property is nil or not. If there is a value (even [NSNumber numberWithBool:YES]), testValue will be true.
On an unrelated note, I also advice to capture and log the error when calling NSManagedObjectContext's save method.
Note that this isn't just a problem with deleted and isDeleted. I wrote a database in which I had a "relationship" property, which was an int16 (choice of several relationship types), but then I also had an "isRelationship" boolean which looked at this property and several others to determine whether the content was about the individual or about a relationship. valueForKey:#"relationship" would return the bool for isRelationship - which meant that it also affected NSPredicate. The following predicate:
NSPredicate *pred = [NSPredicate predicateWithFormat:#"relationship IN %#",
#[[NSNumber numberWithShort:Relationship_Dating],
[NSNumber numberWithShort:Relationship_Married]]];
would fail to filter out friend or family relationships because they were all returning 1 from isRelationship instead of the actual relationship value.
Watch out for stupidly "clever" system behavior when "is" booleans are involved.
(I fixed this by changing the "-(bool) isRelationship" method to "-(bool) isRelational".)
I need to update all the records with the same value. And I don't want to do it with iteration over objects since that takes about 2 seconds for over 2000 objects. Basically I have all users locally and I receive the users id's from the server. I now want to set property 'requested' to false except for the users return from the server-
How it is done now (which is totally wrong!):
Iterating thru all users (about 2000 of them) and correcting their requested value.
NSFetchRequest * allUsers = [[NSFetchRequest alloc] init];
[allUsers setEntity:[NSEntityDescription entityForName:#"User" inManagedObjectContext:context]];
NSArray *users = [context executeFetchRequest:allUsers error:&error];
NSArray *userIDs = [JSON objectForKey:#"users_ids"];
for (User *user in users) {
if ([userIDs containsObject:user.userID]) {
user.requested = [NSNumber numberWithBool:YES];
} else {
user.requested = [NSNumber numberWithBool:NO];
}
user.requested = [NSNumber numberWithBool:NO];
}
[context save:nil];
My idea how to do it:
Set requested=false for every object with a sql-like one statement. Going thru id's returned by the server (20 on average) and setting requested=true for those.
// UPDATE users SET requested = false WHERE 1=1
NSArray *userIDs = [JSON objectForKey:#"users_ids"];
for (int i = 0; i < [userIDs count]; i++) {
User *user = [User userWithServerID:[userIDs[i] integerValue] usingManagedObjectContext:context];
user.requested = [NSNumber numberWithBool:YES];
}
[context save:nil];
So how could I update all the records with one "query" ? Or does anyone have any better idea how to implement this (more performance efficient way of doing the iteration perhaps) ?
Putting this in the background thread is a solution (perhaps), but I would still like to optimise it first before putting it in the background thread.
Thank you!
You would still have to fetch all the objects but you can do it all in one line.
[[fetchedResultsController fetchedObjects] makeObjectsPerformSelector:#selector(setYourValue:) withObject:[NSNumber numberWithBool:YES]];
Or just cycle the array
NSArray *objects = [context executeFetchRequest:allRequest error:&error];
[objects setValue:[NSNumber numberWithBool:YES] forKey:#"user_ids"];
//now save your changes back.
[context save:&error];