I can modify the Reference List attribute of my CKRecord in the dashboard without problems, but how can I modify it programmatically?
I currently try modifying it as a NSArray. It does not give me any sort of error but even though the array content is fine, the attribute does not get set.
Also the documentation on reference lists is either well-hidden or non-existent.
CKReference *reference = [[CKReference alloc] initWithRecord:connectionRecord action:CKReferenceActionNone];
NSMutableArray *list_a = [record_a[#"connections"] mutableCopy];
if (!list_a) list_a = [NSMutableArray array];
[list_a addObject:reference];
record_a[#"connections"] = list_a;
[publicDatabase saveRecord:record_a completionHandler:^(CKRecord *artworkRecord, NSError *error){
if (!error) {
// Insert successfully saved record code
}
else {
// Insert error handling
}
}];
I am thankful for any ideas or suggestions.
Turns out that one has to use CKModifyRecordsOperation to modify existing CKRecords.
Related
So, I'm trying to get all the facebook pictures I'm tagged so I can see them on the iPad, but I wanted to make this function so I can call it everytime I would need to get the url's. The problem is, after I call this function, the array is nil, because the values I get are inside a block. How do I make an array to store the data I get for later use?
-(NSArray *)getFacebookTaggedPictures
{
__block NSArray *taggedPictures = [[NSArray alloc]init];
[FBRequestConnection startWithGraphPath:#"me/photos" completionHandler:^(FBRequestConnection *connection, id result, NSError *error)
{
if(!error)
{
taggedPictures = [(NSArray *)[result data]copy];
//NSLog(#"the tagged pictures are: %#",result);
}
}];
return taggedPictures;
}
As you have correctly noticed, the array that you return is empty because your method returns before the response from the API has been received and parsed into taggedPictures inside the completionHandler block.
You must save the array inside that block. I'd recommend changing your method as follows:
-(NSArray *)getFacebookTaggedPicturesWithCompletion:(void (^)(NSArray* photos))completion;
where the calling methods would pass the appropriate completion block to handle the obtained pictures. (i.e. save them to disk, display them, do some processing on them etc.):
void (^loggerBlock)(NSArray* photos) = ^(NSArray *array) {
NSLog(#"obtained photos: %#",[array description]);
//save here instead of logging
};
[self getFacebookTaggedPicturesWithCompletion:loggerBlock];
Hope this helps.
I have an object, Workout, that has a one-to-many relationship with an object, Exercise.
Diagram of models: http://i.imgur.com/q1Mfq.png
When I create a Workout object, I add three Exercise objects to it by looping over
[self addExercisesObject:exercise]
and then save my managed object context. Then, from my controller for displaying a workout, I can successfully fetch the workout and its exercises (with a fetch request), as shown by the output in my debugger:
Printing description of self->_savedWorkout:
<Workout: 0x6c5a990> (entity: Workout; id: 0x6e46e00 <x-coredata://EA417EAA-101A-4F04-8276-3C4A6CDF094D/Workout/p1> ; data: {
bodyweight = nil;
date = "2012-05-09 16:59:43 +0000";
exercises = (
"0x6e3c870 <x-coredata://EA417EAA-101A-4F04-8276-3C4A6CDF094D/Exercise/p3>",
"0x6e3eaf0 <x-coredata://EA417EAA-101A-4F04-8276-3C4A6CDF094D/Exercise/p2>",
"0x6e36820 <x-coredata://EA417EAA-101A-4F04-8276-3C4A6CDF094D/Exercise/p1>"
);
isCompleted = 0;
workoutId = 1;
workoutPlan = "0x6e6c980 <x-coredata://EA417EAA-101A-4F04-8276-3C4A6CDF094D/WorkoutPlan/p1>";
})
So far so good. However, if I close my app in my simulator and start it up again and perform the same fetch request in same view, the workout looks like this:
Printing description of self->_savedWorkout:
<Workout: 0x6ea8ff0> (entity: Workout; id: 0x6e8f9e0 <x-coredata://EA417EAA-101A-4F04-8276-3C4A6CDF094D/Workout/p1> ; data: {
bodyweight = nil;
date = "2012-05-09 16:59:43 +0000";
exercises = (
);
isCompleted = 0;
workoutId = 1;
workoutPlan = "0x6c8a130 <x-coredata://EA417EAA-101A-4F04-8276-3C4A6CDF094D/WorkoutPlan/p1>";
})
It appears that it fetches the same workout object, but now exercises is an empty set. Actually, exercises first looks like this after the fetch request:
exercises = "<relationship fault: 0x8a93100 'exercises'>";
but once I do:
for (Exercise *exercise in self.savedWorkout.exercises)
self.savedWorkout.exercises resolves to an empty set. I do not edit the workout in anyway in any part of my app.
My fetch request is made by this method in my Workout class:
- (Workout *)getLatestWorkout
{
self.model = [[self.managedObjectContext persistentStoreCoordinator] managedObjectModel];
NSFetchRequest *fetchRequest = [self.model fetchRequestTemplateForName:#"getLatestWorkout"];
NSError *error = nil;
NSArray *results = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if ([results count] == 1) {
return [results objectAtIndex:0];
}
return nil;
}
I made the fetch request template with Xcode's GUI tool. It fetches all Workout objects where isCompleted == 0. You can see that it fetches the same object each time because the workout's x-coredata path is the same in both debugger outputs.
Update: I checked my SQLite database. There is one workout in the workout table and three exercises in the exercises table.
Any ideas what's going on?
EDIT: Code that creates objects posted below
- (void)storeUserSettings
{
// get the file path if it exists
NSString *path = [[NSBundle mainBundle] pathForResource:#"userSettings" ofType:#"plist"];
// create it if it doesn't
if (path == nil) {
path = [NSString stringWithFormat:#"%#%#",
[[NSBundle mainBundle] bundlePath], #"/userSettings.plist"];
}
// and write the new settings to file
[self.userSettings writeToFile:path atomically:YES];
// load managed object context
[self loadMOC];
WorkoutPlan *currentPlan = [[WorkoutPlan alloc] getActiveWorkoutPlan];
[currentPlan setManagedObjectContext:self.managedObjectContext];
// if user has no plan or is changing plans, create new plan and first workout
if (currentPlan == nil ||
([self.userSettings valueForKey:#"plan"] != currentPlan.planId)) {
// create a workoutPlan object
WorkoutPlan *workoutPlan = [[WorkoutPlan alloc] initWithEntity:
[NSEntityDescription entityForName:#"WorkoutPlan"
inManagedObjectContext:self.managedObjectContext]
insertIntoManagedObjectContext:self.managedObjectContext];
// set attributes to values from userSettings and save object
[workoutPlan createWorkoutPlanWithId:[self.userSettings valueForKey:#"plan"]
schedule:[self.userSettings valueForKey:#"schedule"]
dateStarted:[self.userSettings valueForKey:#"nextDate"]];
}
// if user is just changing schedule, update schedule of current plan
else if (![currentPlan.schedule isEqualToString:[self.userSettings valueForKey:#"schedule"]]) {
[currentPlan setSchedule:[self.userSettings valueForKey:#"schedule"]];
[currentPlan saveMOC];
}
}
- (void)loadMOC
{
AppDelegate *delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
self.managedObjectContext = delegate.managedObjectContext;
self.model = [[self.managedObjectContext persistentStoreCoordinator] managedObjectModel];
}
- (void)createWorkoutPlanWithId:(NSNumber *)planId schedule:(NSString *)schedule
dateStarted:(NSDate *)dateStarted
{
[self deactivateCurrentPlan];
// set workout plan attributes
[self setPlanId:planId];
[self setIsActive:[NSNumber numberWithBool:YES]];
[self setSchedule:schedule];
[self setDateStarted:dateStarted];
// create first workout and add to workout plan
Workout *firstWorkout = [NSEntityDescription
insertNewObjectForEntityForName:#"Workout"
inManagedObjectContext:self.managedObjectContext];
[firstWorkout setManagedObjectContext:self.managedObjectContext];
[firstWorkout createFirstWorkoutForPlan:self onDate:dateStarted];
[self addWorkoutsObject:firstWorkout];
[self saveMOC];
}
- (void)createFirstWorkoutForPlan:(WorkoutPlan *)plan onDate:(NSDate *)startDate
{
// set workout attributes
[self setDate:startDate];
[self setIsCompleted:[NSNumber numberWithBool:NO]];
[self setWorkoutId:[NSNumber numberWithInt:1]];
NSArray *exerciseList = [self getExercisesForWorkout:self inPlan:plan];
// iterate over exercises in spec and create them
for (NSDictionary *exerciseSpec in exerciseList)
{
// create a exercise MO
Exercise *exercise = [NSEntityDescription
insertNewObjectForEntityForName:#"Exercise"
inManagedObjectContext:[plan managedObjectContext]];
[exercise setManagedObjectContext:[plan managedObjectContext]];
[exercise createExerciseForWorkout:self withSpec:exerciseSpec];
// add exercise to workout object
[self addExercisesObject:exercise];
}
}
- (void)createExerciseForWorkout:(Workout *)workout withSpec:exerciseSpec
{
// set exercise attributes
self.exerciseId = [exerciseSpec valueForKey:#"id"];
self.isPersonalRecord = [NSNumber numberWithBool:NO];
NSArray *sets = [exerciseSpec valueForKey:#"sets"];
int i = 1;
for (NSNumber *setReps in sets)
{
// create a set MO
Set *set = [NSEntityDescription
insertNewObjectForEntityForName:#"Set"
inManagedObjectContext:[workout managedObjectContext]];
[set setManagedObjectContext:[workout managedObjectContext]];
// set set attributes
set.order = [NSNumber numberWithInt:i];
set.repetitions = setReps;
set.weight = [exerciseSpec valueForKey:#"default_weight"];
// add set to exercise object
[self addSetsObject:set];
i++;
}
}
I had a similar problem. The parent-child relationship worked when the app was running but after re-start only the latest child record was retrieved.
I was adding the children like this:
create the child record
set the child's parent attribute, set the child's other
attributes
add the child to the parent using the parent's add method
I found that it was fixed if I did it like this:
create the child record
add the child to the parent using the parent's add method
set the child's parent attribute, set the child's other
attributes
Core Data is complex. There could be dozens of things to check, any one thing which could be causing issues.
How many MOCs are you using? How are you saving? Many more questions...
I would suggest turning on the SQL debugging flag (-com.apple.CoreData.SQLDebug 1) in the EditScheme for arguments when starting the application.
Run your code, and see what is actually going on.
Relationships resolve to a fault when fetched, unless you override it in the fetch request.
If you are using more than one MOC in a parent/child relationship, the save from the child to the parent just puts data into the parent, it does not really save it. If using UIManagedDocument, it's a whole different set of issues...
I hope this does not sound harsh. Be prepared to provide a whole lot of information for a Core Data question, other than "this is not saving and here is some debugging output."
Basically, how CoreData works depends on how the stack is created, whether using UIManagedDocument or not, multiple threads, how creating objects, how saving them, options on fetch requests, and a whole lot more things.
It's actually not that complex, but there are lots of customizations and special cases depending on how it is used.
EDIT
Post the code that creates the objects/relationships.
Also, try the fetch with a manual fetch request instead of the template. When you look at the data in the database, do you see the foreign keys for the relationships set appropriately?
Run it all again with debugging enabled to see exactly what the SQL is doing. That is more valuable that your own debugging output.
I'm having this exact same problem but my model is pretty complex. My app creates the entities and relationships on startup if they don't already exist. If they are created and I don't exit the app, I'm able to fetch an entity with a to-many relationship and see the correct count of related objects. If I exit my app and restart it (it now knows it doesn't have to create a default set of data) then the relationships are returning a null set. I can't figure it out.
EDIT: I figured out that my problem relates to an Ordered set relation. I had to use a Category to create a work around (found on stack overflow) to insert new entries into an ordered set. So I'm guessing that has something to do with it.
I'm working on a RSS Reader using this tutorial. All table cells data come from a NSMutableArray instance (_allEntries). Then I import
EGOTableViewPullRefresh and add [self refresh] in -(void)reloadTableViewDataSource (self.refresh is a method to populate data of allEntries).
Then pull to refresh works but cells got duplicated every time I refresh. I tried to solve it in two ways.
When download data from internet, add if (![_allEntries containsObject:entry]) before [_allEntries insertObject:entry atIndex:insertIdx] but it didn't work, maybe I should use entry.title or some other attribute in the object to compare but it's not effective.
Like what I did in -viewDidLoad, add self.allEntries = [NSMutableArray array], but I don't know where should I put this line.
Is there anyone who can give me a direction?
[EDIT]
There's no too much logic in viewDidLoad, just
self.allEntries = [NSMutableArray array];
self.queue = [[NSOperationQueue alloc] init]; //add download&parse operation to a queue
self.feeds = [self getFeeds]; //load feeds from local file
And I put [self refresh] in reloadTableViewDataSource, the first time I open my app, there's nothing showed in the tableview. Then I pull to refresh, it works. Then pull to refresh again, it got duplicated.This is the "refresh" method.
- (void)refresh {
for (NSString *feed in _feeds) {
NSURL *url = [NSURL URLWithString:feed];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[_queue addOperation:request];
}
}
I want to rebuild the array so I write self.allEntries = [NSMutableArray array] again but it turns out "Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (140)". So as mentioned, I really get confused about where should I put this line.Thx~~
The logic you have in viewDidLoad that builds your array should be moved to its own method (reloadTableViewData), and then you would just call that method in viewDidLoad.
[self reloadTableViewData];
You would also call that same method when you do the pull to refresh.
Make sure you are rebuilding that array and not just adding objects to the existing one.
Is it possible to make a Core Data attribute unique, i.e. no two MyEntity objects can have the same myAttribute?
I know how to enforce this programatically, but I'm hoping there's a way to do it using the graphical Data Model editor in xcode.
I'm using the iPhone 3.1.2 SDK.
Every time i create on object I perform a class method that makes a new Entity only when another one does not exist.
+ (TZUser *)userWithUniqueUserId:(NSString *)uniqueUserId inManagedObjectContext:(NSManagedObjectContext *)context
{
TZUser *user = nil;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"TZUser" inManagedObjectContext:context];
request.predicate = [NSPredicate predicateWithFormat:#"objectId = %#", uniqueUserId];
NSError *executeFetchError = nil;
user = [[context executeFetchRequest:request error:&executeFetchError] lastObject];
if (executeFetchError) {
NSLog(#"[%#, %#] error looking up user with id: %i with error: %#", NSStringFromClass([self class]), NSStringFromSelector(_cmd), [uniqueUserId intValue], [executeFetchError localizedDescription]);
} else if (!user) {
user = [NSEntityDescription insertNewObjectForEntityForName:#"TZUser"
inManagedObjectContext:context];
}
return user;
}
From IOS 9 there is a new way to handle unique constraints.
You define the unique attributes in the data model.
You need to set a managed context merge policy "Merge policy singleton objects that define standard ways to handle conflicts during a save operation" NSErrorMergePolicy is the default,This policy causes a save to fail if there are any merge conflicts.
- (NSManagedObjectContext *)managedObjectContext {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.)
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (!coordinator) {
return nil;
}
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
[_managedObjectContext setMergePolicy:NSOverwriteMergePolicy];
return _managedObjectContext;
}
The various option are discussed at Apple Ducumentation Merge Policy
It is answered nicely here
Zachary Orr's Answer
and he has kindly also created a blogpost and sample code.
Sample Code
Blog Post
The most challenging part is to get the data Model attributes editable.The Secret is to left click and then right click, after you have clicked the + sign to add a constraint.
I've decided to use the validate<key>:error: method to check if there is already a Managed Object with the specific value of <key>. An error is raised if this is the case.
For example:
- (BOOL)validateMyAttribute:(id *)value error:(NSError **)error {
// Return NO if there is already an object with a myAtribute of value
}
Thanks to Martin Cote for his input.
You could override the setMyAttribute method (using categories) and ensure uniqueness right there, although this may be expensive:
- (void)setMyAttribute:(id)value
{
NSArray *objects = [self fetchObjectsWithMyValueEqualTo:value];
if( [objects count] > 0 ) // ... throw some exception
[self setValue:value forKey:#"myAttribute"];
}
If you want to make sure that every MyEntity instance has a distinct myAttribute value, you can use the objectID of the NSManagedObject objects for that matter.
I really liked #DoozMen approach!!
I think it's the easiest way to do what i needed to do.
This is the way i fitted it into my project:
The following code cycles while drawing a quite long tableView, saving to DB an object for each table row, and setting various object attributes for each one, like UISwitch states and other things: if the object for the row with a certain tag is not present inside the DB, it creates it.
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"Obiettivo" inManagedObjectContext:self.managedObjectContext];
request.predicate = [NSPredicate predicateWithFormat:#"obiettivoID = %d", obTag];
NSError *executeFetchError = nil;
results = [[self.managedObjectContext executeFetchRequest:request error:&executeFetchError] lastObject];
if (executeFetchError) {
NSLog(#"[%#, %#] error looking up for tag: %i with error: %#", NSStringFromClass([self class]), NSStringFromSelector(_cmd), obTag, [executeFetchError localizedDescription]);
} else if (!results) {
if (obbCD == nil) {
NSEntityDescription *ent = [NSEntityDescription entityForName:#"Obiettivo" inManagedObjectContext:self.managedObjectContext];
obbCD = [[Obiettivo alloc] initWithEntity:ent insertIntoManagedObjectContext:self.managedObjectContext];
}
//set the property that has to be unique..
obbCD.obiettivoID = [NSNumber numberWithUnsignedInt:obTag];
[self.managedObjectContext insertObject:obbCD];
NSError *saveError = nil;
[self.managedObjectContext save:&saveError];
NSLog(#"added with ID: %#", obbCD.obiettivoID);
obbCD = nil;
}
results = nil;
Take a look at the Apple documentation for inter-property validation. It describes how you can validate a particular insert or update operation while being able to consult the entire database.
You just have to check for an existing one :/
I just see nothing that core data really offers that helps with this. The constraints feature, as well as being broken, doesn't really do the job. In all real-world circumstances you simply need to, of course, check if one is there already and if so use that one (say, as the relation field of another item, of course). I just can't see any other approach.
To save anyone typing...
// you've download 100 new guys from the endpoint, and unwrapped the json
for guy in guys {
// guy.id uniquely identifies
let g = guy.id
let r = NSFetchRequest<NSFetchRequestResult>(entityName: "CD_Guy")
r.predicate = NSPredicate(format: "id == %d", g)
var found: [CD_Guy] = []
do {
let f = try core.container.viewContext.fetch(r) as! [CD_Guy]
if f.count > 0 { continue } // that's it. it exists already
}
catch {
print("basic db error. example, you had = instead of == in the pred above")
continue
}
CD_Guy.make(from: guy) // just populate the CD_Guy
save here: core.saveContext()
}
or save here: core.saveContext()
core is just your singleton, whatever holding your context and other stuff.
Note that in the example you can saveContext either each time there's a new one added, or, all at once afterwards.
(I find tables/collections draw so fast, in conjunction with CD, it's really irrelevant.)
(Don't forget about .privateQueueConcurrencyType )
Do note that this example DOES NOT show that you, basically, create the entity and write on another context, and you must use .privateQueueConcurrencyType You can't use the same context as your tables/collections .. the .viewContext .
let pmoc = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
pmoc.parent = core.container.viewContext
do { try pmoc.save() } catch { fatalError("doh \(error)")}
I've looked through all the class documentation for Core Data and I can't find away to programmatically update values in a core data entity. For example, I have a structure similar to this:
id | title
============
1 | Foo
2 | Bar
3 | FooFoo
Say that I want to update Bar to BarBar, I can't find any way to do this in any of the documentation.
In Core Data, an object is an object is an object - the database isn't a thing you throw commands at.
To update something that is persisted, you recreate it as an object, update it, and save it.
NSError *error = nil;
//This is your NSManagedObject subclass
Books * aBook = nil;
//Set up to get the thing you want to update
NSFetchRequest * request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"MyLibrary" inManagedObjectContext:context]];
[request setPredicate:[NSPredicate predicateWithFormat:#"Title=%#",#"Bar"]];
//Ask for it
aBook = [[context executeFetchRequest:request error:&error] lastObject];
[request release];
if (error) {
//Handle any errors
}
if (!aBook) {
//Nothing there to update
}
//Update the object
aBook.Title = #"BarBar";
//Save it
error = nil;
if (![context save:&error]) {
//Handle any error with the saving of the context
}
The Apple documentation on using managed objects in Core Data likely has your answer. In short, though, you should be able to do something like this:
NSError *saveError;
[bookTwo setTitle:#"BarBar"];
if (![managedObjectContext save:&saveError]) {
NSLog(#"Saving changes to book book two failed: %#", saveError);
} else {
// The changes to bookTwo have been persisted.
}
(Note: bookTwo must be a managed object that is associated with managedObjectContext for this example to work.)
Sounds like you're thinking in terms of an underlying relational database. Core Data's API is built around model objects, not relational databases.
An entity is a Cocoa object—an instance of NSManagedObject or some subclass of that. The entity's attributes are properties of the object. You use key-value coding or, if you implement a subclass, dot syntax or accessor methods to set those properties.
Evan DiBiase's answer shows one correct way to set the property—specifically, an accessor message. Here's dot syntax:
bookTwo.title = #"BarBar";
And KVC (which you can use with plain old NSManagedObject):
[bookTwo setValue:#"BarBar" forKey:#"title"];
If I'm understanding your question correctly, I think that all you need to keep in mind is managed objects are really no different than any other Cocoa class. Attributes have accessors and mutators you can use in code, through key value coding or through bindings, only in this case they're generated by Core Data. The only trick is you need to manually declare the generated accessors in your class file (if you have one) for your entity if you want to avoid having to use setValue:ForKey:. The documentation describes this in more detail, but the short answer is that you can select your attributes in the data model designer, and choose Copy Obj-C 2.0 Method Declarations from the Design menu.
NSManagedObject *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSArray *temp = [[NSArray alloc]initWithObjects:#"NewWord", nil];
[object setValue:[temp objectAtIndex:0] forKey:#"title"];
// Save the context.
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
I think this piece of code will give you the idea ;)