moveRowAtIndexPath and CoreData Relationships - xcode

I'm stuck at ordering Core Data relationships. I have a to-many relationship but the problem I can't overcome is that re-ordering relational entities in one view re-orders them in every view they belong to.
Example: People are added to activity1 and are randomly ordered using the moveRowAtIndexPath method. Those same people get added to activity2 and are ordered identically to activity1. How can I stop this?
Relevant code:
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
NSUInteger fromRow = [fromIndexPath row];
NSUInteger toRow = [toIndexPath row];
id object = [[eventAthletes objectAtIndex:fromRow] retain];
[eventAthletes removeObjectAtIndex:fromRow];
[eventAthletes insertObject:object atIndex:toRow];
int i = 0;
for (object in eventAthletes)
{
[object setValue:[NSNumber numberWithInt:i++] forKey:#"athleteDisplayOrder"];
}
[self saveContext];
[object release];
}
I updated the model and made the attribute athleteDisplayOrder transient thinking that by using the key and NOT saving it, I would be able to start fresh in a new event. No Dice.

The identical ordering is expected behavior. If you want to "randomize" the order, you have to explicitly do that by reassigning random athleteDisplayOrderattributes.
EDIT:
If you want to have a special user definable order of athletes for each event one way to solve this is to create a new entity EventAthletes that has to-one relationships both Event and Athlete and also contains a display order attribute.
END EDIT
BTW, you are using an extra data array with core data for display in a table view. Did you know that you can use NSFetchedResultsController to manage your table view datasource much more efficiently?

Related

Filtering CoreData Fetch with parentEntities

I have a Problem with my CoreData Model:
If i have a lot of TopObjects and want to fetch all SubObject's which have a relationship to a specific object, how can i filter my results in the fetch predicate.
normally i would set a predicate like "top = refObject". but the abstract entity SubObject has no relationship "top", just the entities itself.
If i try to add the relationship only to the parent entity "SubObject" i lost the direct Relations in the TopEntity.
Can anybody give me a hint ?
Not sure if there's a way of conditionally specifying a relationship in the predicate (which would allow a single fetch request to do this), but below is possibly a way to fetch the objects you need in multiple fetches. The idea is to iterate through all of the entities in the managed object model and check if they have the TopObject relationship and are of the SubObject class, then fetch them based on the topObject.
for (NSEntityDescription *entityDescription in managedObjectModel)
{
// Attempt to pull out the TopObject relationship
NSRelationshipDescription *topRelationshipDescription = entityDescription.relationshipsByName[#"top"];
// Test if the relationship points to the TopObject and if the entity is of the correct class
if ([topRelationshipDescription.destinationEntity.name isEqualToString:#"TopObject"] &&
[NSClassFromString(entityDescription.managedObjectClassName) isSubclassOfClass:[SubObject class]])
{
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:entityDescription.name];
fetchRequest.predicate = [NSPredicate predicateWithFormat:#"top = %#", topObject];
// fetch objects and add them to an array
}
}

Saving changes to a NSManagedObject

I have a problem with making changes to ManagedObjects and saving those changes to the persistent store.
What does work is deleting objects, inserting object. and fetching those objects. As said, i fail making a change to a fetched managed object.
I have two view controllers.
Both have a public property:
#property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
The AppDelegate.m file sets this property for the first view controller in the application:didFinishLaunchingWithOptions: method like so:
// get reference to view controller //
//..
controller1.managedObjectContext = self.managedObjectContext;
view controller1 again passes the managedObjectContext when pushing to the second view controller, it does this in the prepareForSegue: method like so:
// Get reference to the detail view //
MySecondViewController *controller2 = segue.destinationViewController;
// Pass the managed object context //
controller2.managedObjectContext = self.managedObjectContext;
Within this second view controller I fetch objects from core data and I store them in a property of the view controller like this:
#property (nonatomic, strong) MyKindOfObject *object;
...
self.object = [fetchResults lastObject];
Fetching objects seems to work fine as i nicely get results..
The fetched object has a number of properties, one of them is of type NSString. The value of this property I show in a UITextField. The user can change the value here and when done he presses a button. I catch the action and I do the following:
self.object.mytext = textField.text;
followed by trying to save to core data:
// Save to CoreData //
NSError *error = nil;
if(![self.managedObjectContext save:&error]){
// handle the error //
NSLog(#"is error");
}
else{
NSLog(#"no error");
}
1) The next time the user returns to this view controller, again the object will be fetched. But it still has the old value.
2) Also I use a Firefox add-on called SQLite Manager to keep an eye on the data within the related sqlite file. When stepping through the code, after calling the save method nothing changes in the file.
3) Also within Xcode i use the Variables View to keep an eye on the self.managedObjectContext object. When I am storing the new data into my object, right before calling save, none of the properties of self.managedObjectContext change (like unprocessedChanges or changedObjects).
4) I also added a call to the hasChanges method of the managedObjectContext, but this returns NO:
if([self.managedObjectContext hasChanges]){
NSLog(#"changes managed object context!");
}
5) I also added a call to the hasChanges method of the related managedObject, but this also returns NO:
if([self.object hasChanges]){
NSLog(#"changes in managed object!");
}
I am probably doing something totally wrong here but I can not figure out what it is.
I truly hope somebody can help me out?.
Thanks in advance!
Oke let me answer my own question: as the newby that I am, i indeed did something totally wrong. I was thinking I knew better then apple themselves. lesson learned: I do not!
After creating entities within the model I created custom managed object classes for each entity (or actually NSManagedObject subclasses for each entity).
Within the generated implementation files all properties (attributes) are implemented as #dynamic. Due to lack of knowledge I did not understand/recognise this. So the newby in me thought: lets change all that to #synthesize.
Hopefully this post can help some other newby to not make the same mistake.

Create entity programmatically and add it to an array controller

I have an array controller which is bound to an nstableview. I also have some nstextfields which the user populates then presses an "add" button. I want to take those fields, first_name and last_name, and use them to populate an entity. I'll call the entity PersonEntity.
So in the delegate for the add button I get the string values for the 2 text fields, populate an entity, then add it to the array controller. I'm new to cocoa/objective-c. This seems like a straightforward thing but it appears that I cannot create an entity like I expect
PersonEntity* person
[person setFirst_name:firstName];
[person setLast_name:lastName];
[customerArray addObject:person];
It crashes saying I can't add nil at the [customerArray addObject:customer] line. That line is my attempt to add the entity to the array controller which is bound to the tableview. What is the correct way to do something like this?
I'm not sure if PersonEntity is a Core Data Entity, but since you question is also tagged as Core Data, I'll assume it is.
If your ArrayController (the one bound to your NSTableView), is bound to CoreData source, you don't add objects directly to it. Instead you add it to your managedObjectContext and it will reflect on your NSTableView.
The code should look like this:
PersonEntity *person = [NSEntityDescription insertNewObjectForEntityForName:#"PersonEntity"
inManagedObjectContext: managedObjectContext];
[person setFirst_name:firstName];
[person setLast_name:lastName];
/* Save Event */
NSError * error = nil;
[__managedObjectContext save: &error];
Hope this helps!
Mane

Add editing behaviors to bound NSFormCell

I've a Core Data model class with a customer ID attribute. It's bound to a form cell. When the user finishes editing the text I want a chance to convert their entry to upper case, using logic which depends on the old and new values.
Ideally I want to keep the behavior close to the view where it belongs, using an object I can instantiate in the nib and hook up to the text cells. But I'd settle for an object I had to hook up to the model.
I've implemented this three different ways:
Custom setter method in the model class
Text editing delegate implementing NSControlTextEditingDelegate
Helper class which uses KVO to notice the change and initiate a subsequent change
All three implementations have problems. The issues, respectively:
This behavior doesn't belong in the model. I should be able to set the attribute in code, for example, without triggering it.
I can't get the "before" value because the form cell doesn't provide controlTextDidBeginEditing: calls (and the old value is gone by the time controlTextDidEndEditing: is called). Furthermore tabbing in and out of the field without typing anything triggers a call to controlTextDidEndEditing:.
When the observation fires for the user's change, and I initiate a subsequent change to that property, the view ignores the change notification and doesn't redraw. (I presume the binder does this for efficiency. Normally when updating the model, it can ignore the KVO observations from the field being updated.)
How would you solve this problem?
After some discussion here, it sounds like some possible ways to do tho:
Put a category on the model class and override validateMyKey
Subclassing NSFormCell
I tried both. More issues:
validateMyKey isn't called until after the model updates itself, so the old value isn't available.
editWithFrame:inView:editor:delegate:event: isn't always called upon entering a field, so it's difficult to access the old value in endEditing:.
New solution is a refinement on my original #2: text editing delegate implementing NSControlTextEditingDelegate.
Instead of controlTextDidBeginEditing: and controlTextDidEndEditing:, implement only control:textShouldEndEditing:. In that method, manipulate the text if necessary, then return YES.
I instantiate this in the nib and make it the form's delegate (not the cells'). In the code below I get the old value using infoForBinding: but if you aren't using bindings, you could add an outlet to the model object instead.
-(BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor {
NSCell *cell = [(NSForm *)control selectedCell];
NSString *identifier = [(NSCell *)[(NSForm *)control selectedCell] identifier];
if (!identifier) return YES;
NSDictionary *bindingInfo = [cell infoForBinding:#"value"];
if (!bindingInfo) return YES;
NSString *oldValue = [[bindingInfo valueForKey:NSObservedObjectKey] valueForKeyPath:[bindingInfo valueForKey:NSObservedKeyPathKey]];
NSString *newValue = cell.stringValue;
if ([identifier isEqualTo:#"firstField"]) {
if (criteria)
cell.stringValue = ....;
} else if ([identifier isEqualTo:#"secondField"]) {
if (criteria)
cell.stringValue = ....;
}
return YES;
}

Iterate through NSManagedObjectContext objects?

I want to iterate through all of the objects in my NSManagedObjectContext, and update them manually. Then, every managed object should be updated.
What's the best way to do this?
Theoretically you could iterate through all the entity descriptions in your managed object model, build a no-predicate fetch request for them, then loop over all the returned objects and do some update. Example:
// Given some NSManagedObjectContext *context
NSManagedObjectModel *model = [[context persistentStoreCoordinator]
managedObjectModel];
for(NSEntityDescription *entity in [model entities]) {
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entity];
NSError *error;
NSArray *results = [context executeFetchRequest:request error:&error];
// Error-checking here...
for(NSManagedObject *object in results) {
// Do your updates here
}
}
Note you can cast the NSManagedObjects returned as necessary either by testing for class equality (using isKindOfClass: or a related method) or by figuring out what class the current entity is (using the managedObjectClassName property on entity in conjunction with the NSClassWithName() method).
This seems like a very heavy handed approach to the problem. If the data is getting loaded with bad data then i would strongly suggest fixing it while you are importing the data. Tim's answer will work for what you are doing but I strongly suspect you are coming at this wrong. Iterrating through the whole database looking for potentially bad data is very inefficient.
If managed objects are getting created with the 'wrong data', I would check to make sure you've set default values in your model for all properties of all entities. This way you can be sure whenever you insert an object into your context it will contain those values. From there, you can set the properties to whatever you need.

Resources